mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-10 09:00:41 +01:00
Merge branch 'develop' into feature/mna/PSF-884-location-view
This commit is contained in:
commit
afd2804ed3
3
.github/ISSUE_TEMPLATE/release.yml
vendored
3
.github/ISSUE_TEMPLATE/release.yml
vendored
@ -23,7 +23,8 @@ body:
|
||||
|
||||
### Do the release
|
||||
|
||||
- [ ] Create release with gitflow, branch name `release/1.2.3`
|
||||
- [ ] Make sure `develop` and `main` are up to date (git pull)
|
||||
- [ ] Checkout develop and create a release with gitflow, branch name `release/1.2.3`
|
||||
- [ ] Check the crashes from the PlayStore
|
||||
- [ ] Check the rageshake with the current dev version: https://github.com/matrix-org/element-android-rageshakes/labels/1.2.3-dev
|
||||
- [ ] Run the integration test, and especially `UiAllScreensSanityTest.allScreensTest()`
|
||||
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@ -10,6 +10,8 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
reviewers:
|
||||
- "vector-im/element-android-reviewers"
|
||||
ignore:
|
||||
- dependency-name: "*github-script*"
|
||||
# Updates for Gradle dependencies used in the app
|
||||
@ -19,6 +21,6 @@ updates:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 200
|
||||
reviewers:
|
||||
- "bmarty"
|
||||
- "vector-im/element-android-reviewers"
|
||||
ignore:
|
||||
- dependency-name: com.google.zxing:core
|
||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -67,4 +67,4 @@ jobs:
|
||||
path: |
|
||||
vector/build/outputs/apk/*/release/*.apk
|
||||
|
||||
# TODO: add exodus checks
|
||||
# TODO add exodus checks
|
||||
|
22
.github/workflows/docs.yml
vendored
Normal file
22
.github/workflows/docs.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
name: Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: Generate and publish Android Matrix SDK documentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build docs
|
||||
run: ./gradlew dokkaHtml
|
||||
|
||||
- name: Deploy docs
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./matrix-sdk-android/build/dokka/html
|
@ -1,28 +1,43 @@
|
||||
name: Nightly Tests
|
||||
name: Integration Tests
|
||||
|
||||
# This runs for all closed pull requests against main, including those closed without merge.
|
||||
# Further filtering occurs in 'should-i-run'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ release/* ]
|
||||
schedule:
|
||||
# At 20:00 every day UTC
|
||||
- cron: '0 20 * * *'
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches: [develop]
|
||||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
CI_GRADLE_ARG_PROPERTIES: >
|
||||
-Porg.gradle.jvmargs=-Xmx4g
|
||||
-Porg.gradle.parallel=false
|
||||
|
||||
jobs:
|
||||
|
||||
# More info on should-i-run:
|
||||
# If this fails to run (the IF doesn't complete) then the needs will not be satisfied for any of the
|
||||
# other jobs below, so none will run.
|
||||
# except for the notification job at the bottom which will run all the time, unless should-i-run isn't
|
||||
# successful, or all the other jobs have succeeded
|
||||
|
||||
should-i-run:
|
||||
name: Check if PR is suitable for analysis
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged # Additionally require PR to have been completely merged.
|
||||
steps:
|
||||
- run: echo "Run those tests!" # no-op success
|
||||
|
||||
# Run Android Tests
|
||||
integration-tests:
|
||||
name: Matrix SDK - Running Integration Tests
|
||||
needs: should-i-run
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
api-level: [ 28 ]
|
||||
# No concurrency required, runs every time on a schedule.
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
@ -210,6 +225,7 @@ jobs:
|
||||
|
||||
ui-tests:
|
||||
name: UI Tests (Synapse)
|
||||
needs: should-i-run
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -268,7 +284,8 @@ jobs:
|
||||
|
||||
codecov-units:
|
||||
name: Unit tests with code coverage
|
||||
runs-on: macos-latest
|
||||
needs: should-i-run
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
@ -292,49 +309,21 @@ jobs:
|
||||
path: |
|
||||
build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml
|
||||
|
||||
sonarqube:
|
||||
name: Sonarqube upload
|
||||
runs-on: macos-latest
|
||||
if: always() && github.event_name == 'schedule'
|
||||
needs:
|
||||
- codecov-units
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: codecov-xml # will restore to allCodeCoverageReport.xml by default; we restore to the same location in following tasks
|
||||
- run: mkdir -p build/reports/jacoco/allCodeCoverageReport/
|
||||
- run: mv allCodeCoverageReport.xml build/reports/jacoco/allCodeCoverageReport/
|
||||
- run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
||||
# Notify the channel about scheduled runs, or pushes to the release branches, do not notify for manually triggered runs
|
||||
# Notify the channel about delayed failures
|
||||
notify:
|
||||
name: Notify matrix
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- should-i-run
|
||||
- integration-tests
|
||||
- ui-tests
|
||||
- sonarqube
|
||||
if: always() && github.event_name != 'workflow_dispatch'
|
||||
- codecov-units
|
||||
if: always() && (needs.should-i-run.result == 'success' ) && ((needs.codecov-units.result != 'success' ) || (needs.ui-tests.result != 'success') || (needs.integration-tests.result != 'success'))
|
||||
# No concurrency required, runs every time on a schedule.
|
||||
steps:
|
||||
- uses: michaelkaye/matrix-hookshot-action@v1.0.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
hookshot_url: ${{ secrets.ELEMENT_ANDROID_HOOKSHOT_URL }}
|
||||
text_template: "{{#if '${{ github.event_name == 'schedule' }}' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
|
||||
html_template: "{{#if '${{ github.event_name == 'schedule' }}' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }}<br />{{icon conclusion}} {{name}} <font color='{{color conclusion}}'>{{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a></font>{{/if}}{{/with}}{{/each}}"
|
||||
text_template: "Post-merge validation of ${{ github.head_ref }} into ${{ github.base_ref }} by ${{ github.event.pull_request.merged_by }} failed: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
|
||||
html_template: "Post-merge validation of ${{ github.head_ref }} into ${{ github.base_ref }} by ${{ github.event.pull_request.merged_by }} failed: {{#each job_statuses }}{{#with this }}{{#if completed }}<br />{{icon conclusion}} {{name}} <font color='{{color conclusion}}'>{{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a></font>{{/if}}{{/with}}{{/each}}"
|
30
.github/workflows/quality.yml
vendored
30
.github/workflows/quality.yml
vendored
@ -14,6 +14,16 @@ jobs:
|
||||
- name: Run code quality check suite
|
||||
run: ./tools/check/check_code_quality.sh
|
||||
|
||||
# Knit for all the modules (https://github.com/Kotlin/kotlinx-knit)
|
||||
knit:
|
||||
name: Knit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run knit
|
||||
run: |
|
||||
./gradlew knit
|
||||
|
||||
# ktlint for all the modules
|
||||
ktlint:
|
||||
name: Kotlin Linter
|
||||
@ -147,3 +157,23 @@ jobs:
|
||||
name: release-lint-report-${{ matrix.target }}
|
||||
path: |
|
||||
vector/build/reports/*.*
|
||||
|
||||
detekt:
|
||||
name: Detekt Analysis
|
||||
runs-on: ubuntu-latest
|
||||
# Allow all jobs on main and develop. Just one per PR.
|
||||
concurrency:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('detekt-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('detekt-develop-{0}', github.sha) || format('detekt-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run detekt
|
||||
run: |
|
||||
./gradlew detekt
|
||||
- name: Upload reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: detekt-report
|
||||
path: |
|
||||
*/build/reports/detekt/detekt.html
|
||||
|
81
.github/workflows/sonarqube.yml
vendored
Normal file
81
.github/workflows/sonarqube.yml
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
name: Sonarqube nightly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 20 * * *'
|
||||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
CI_GRADLE_ARG_PROPERTIES: >
|
||||
-Porg.gradle.jvmargs=-Xmx4g
|
||||
-Porg.gradle.parallel=false
|
||||
jobs:
|
||||
codecov-units:
|
||||
name: Unit tests with code coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- run: ./gradlew allCodeCoverageReport $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Upload Codecov data
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: codecov-xml
|
||||
path: |
|
||||
build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml
|
||||
|
||||
sonarqube:
|
||||
name: Sonarqube upload
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- codecov-units
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: codecov-xml # will restore to allCodeCoverageReport.xml by default; we restore to the same location in following tasks
|
||||
- run: mkdir -p build/reports/jacoco/allCodeCoverageReport/
|
||||
- run: mv allCodeCoverageReport.xml build/reports/jacoco/allCodeCoverageReport/
|
||||
- run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
||||
# Notify the channel about sonarqube failures
|
||||
notify:
|
||||
name: Notify matrix
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- sonarqube
|
||||
- codecov-units
|
||||
if: always() && (needs.sonarqube.result != 'success' || needs.codecov-units.result != 'success')
|
||||
steps:
|
||||
- uses: michaelkaye/matrix-hookshot-action@v1.0.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
hookshot_url: ${{ secrets.ELEMENT_ANDROID_HOOKSHOT_URL }}
|
||||
text_template: "Sonarqube run (on ${{ github.ref }}): {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
|
||||
html_template: "Sonarqube run (on ${{ github.ref }}): {{#each job_statuses }}{{#with this }}{{#if completed }}<br />{{icon conclusion}} {{name}} <font color='{{color conclusion}}'>{{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a></font>{{/if}}{{/with}}{{/each}}"
|
@ -14,7 +14,7 @@ It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-androi
|
||||
[<img src="resources/img/google-play-badge.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.app)
|
||||
[<img src="resources/img/f-droid-badge.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.app)
|
||||
|
||||
Nightly build: [![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop) Nighly test status: [![allScreensTest](https://github.com/vector-im/element-android/actions/workflows/nightly.yml/badge.svg)](https://github.com/vector-im/element-android/actions/workflows/nightly.yml)
|
||||
Nightly build: [![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop) Nightly test status: [![allScreensTest](https://github.com/vector-im/element-android/actions/workflows/nightly.yml/badge.svg)](https://github.com/vector-im/element-android/actions/workflows/nightly.yml)
|
||||
|
||||
|
||||
# New Android SDK
|
||||
@ -53,3 +53,4 @@ Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/
|
||||
Issues are triaged by community members and the Android App Team, following the [triage process](https://github.com/vector-im/element-meta/wiki/Triage-process).
|
||||
|
||||
We use [issue labels](https://github.com/vector-im/element-meta/wiki/Issue-labelling) to sort all incoming issues.
|
||||
|
||||
|
35
build.gradle
35
build.gradle
@ -5,10 +5,17 @@ buildscript {
|
||||
apply from: 'dependencies_groups.gradle'
|
||||
|
||||
repositories {
|
||||
google()
|
||||
// Do not use `google()`, it prevents Dependabot from working properly
|
||||
maven {
|
||||
url 'https://maven.google.com'
|
||||
}
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
// Do not use `mavenCentral()`, it prevents Dependabot from working properly
|
||||
maven {
|
||||
url 'https://repo1.maven.org/maven2'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -23,14 +30,17 @@ buildscript {
|
||||
classpath "com.likethesalad.android:stem-plugin:2.0.0"
|
||||
classpath 'org.owasp:dependency-check-gradle:7.1.0.1'
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21"
|
||||
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
// ktlint Plugin
|
||||
plugins {
|
||||
// ktlint Plugin
|
||||
id "org.jlleitschuh.gradle.ktlint" version "10.3.0"
|
||||
// Detekt
|
||||
id "io.gitlab.arturbosch.detekt" version "1.20.0"
|
||||
}
|
||||
|
||||
// https://github.com/jeremylong/DependencyCheck
|
||||
@ -45,8 +55,10 @@ dependencyCheck {
|
||||
|
||||
allprojects {
|
||||
apply plugin: "org.jlleitschuh.gradle.ktlint"
|
||||
apply plugin: "io.gitlab.arturbosch.detekt"
|
||||
|
||||
repositories {
|
||||
// Do not use `mavenCentral()`, it prevents Dependabot from working properly
|
||||
maven {
|
||||
url 'https://repo1.maven.org/maven2'
|
||||
content {
|
||||
@ -71,14 +83,18 @@ allprojects {
|
||||
groups.jitsi.group.each { includeGroup it }
|
||||
}
|
||||
}
|
||||
google {
|
||||
// Do not use `google()`, it prevents Dependabot from working properly
|
||||
maven {
|
||||
url 'https://maven.google.com'
|
||||
content {
|
||||
groups.google.regex.each { includeGroupByRegex it }
|
||||
groups.google.group.each { includeGroup it }
|
||||
}
|
||||
}
|
||||
//noinspection JcenterRepositoryObsolete
|
||||
jcenter {
|
||||
// Do not use `jcenter`, it prevents Dependabot from working properly
|
||||
maven {
|
||||
url 'https://jcenter.bintray.com'
|
||||
content {
|
||||
groups.jcenter.regex.each { includeGroupByRegex it }
|
||||
groups.jcenter.group.each { includeGroup it }
|
||||
@ -107,7 +123,7 @@ allprojects {
|
||||
// display the corresponding rule
|
||||
verbose = true
|
||||
disabledRules = [
|
||||
// TODO: Re-enable these 4 rules after reformatting project
|
||||
// TODO Re-enable these 4 rules after reformatting project
|
||||
"indent",
|
||||
"experimental:argument-list-wrapping",
|
||||
"max-line-length",
|
||||
@ -128,6 +144,15 @@ allprojects {
|
||||
"experimental:kdoc-wrapping",
|
||||
]
|
||||
}
|
||||
|
||||
detekt {
|
||||
// preconfigure defaults
|
||||
buildUponDefaultConfig = true
|
||||
// activate all available (even unstable) rules.
|
||||
allRules = true
|
||||
// point to your custom config defining rules to run, overwriting default behavior
|
||||
config = files("$rootDir/tools/detekt/detekt.yml")
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
|
1
changelog.d/5494.feature
Normal file
1
changelog.d/5494.feature
Normal file
@ -0,0 +1 @@
|
||||
Use key backup before requesting keys + refactor & improvement of key request/forward
|
4
changelog.d/5559.sdk
Normal file
4
changelog.d/5559.sdk
Normal file
@ -0,0 +1,4 @@
|
||||
- New API to enable/disable key forwarding CryptoService#enableKeyGossiping()
|
||||
- New API to limit room key request only to own devices MXCryptoConfig#limitRoomKeyRequestsToMyDevices
|
||||
- Event Trail API has changed, now using AuditTrail events
|
||||
- New API to manually accept an incoming key request CryptoService#manuallyAcceptRoomKeyRequest()
|
1
changelog.d/5825.bugfix
Normal file
1
changelog.d/5825.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Changed copy and list order in member profile screen.
|
1
changelog.d/5887.sdk
Normal file
1
changelog.d/5887.sdk
Normal file
@ -0,0 +1 @@
|
||||
Small change in the Matrix class: deprecated methods have been removed and the constructor is now public. Also the fun `workerFactory()` has been renamed to `getWorkerFactory()`
|
1
changelog.d/5906.bugfix
Normal file
1
changelog.d/5906.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Desynchronized 4S | Megolm backup causing Unusable backup
|
1
changelog.d/5911.feature
Normal file
1
changelog.d/5911.feature
Normal file
@ -0,0 +1 @@
|
||||
Screen sharing over WebRTC
|
1
changelog.d/5932.feature
Normal file
1
changelog.d/5932.feature
Normal file
@ -0,0 +1 @@
|
||||
Allow using the latest user Avatar and name for all messages in the timeline
|
1
changelog.d/5936.feature
Normal file
1
changelog.d/5936.feature
Normal file
@ -0,0 +1 @@
|
||||
Added themed launch icons for Android 13
|
1
changelog.d/5941.bugfix
Normal file
1
changelog.d/5941.bugfix
Normal file
@ -0,0 +1 @@
|
||||
If animations are disable on the System, chat effects and confetti will be disabled too
|
1
changelog.d/5965.sdk
Normal file
1
changelog.d/5965.sdk
Normal file
@ -0,0 +1 @@
|
||||
Including SSL/TLS error handing when doing WellKnown lookups without a custom HomeServerConnectionConfig
|
1
changelog.d/5973.doc
Normal file
1
changelog.d/5973.doc
Normal file
@ -0,0 +1 @@
|
||||
Note public_baseurl requirement in integration tests documentation.
|
1
changelog.d/5997.misc
Normal file
1
changelog.d/5997.misc
Normal file
@ -0,0 +1 @@
|
||||
Update check for server-side threads support to match spec.
|
1
changelog.d/6038.misc
Normal file
1
changelog.d/6038.misc
Normal file
@ -0,0 +1 @@
|
||||
Setup detekt
|
1
changelog.d/6047.feature
Normal file
1
changelog.d/6047.feature
Normal file
@ -0,0 +1 @@
|
||||
Add presence indicator busy and away.
|
@ -7,26 +7,26 @@ ext.versions = [
|
||||
'targetCompat' : JavaVersion.VERSION_11,
|
||||
]
|
||||
|
||||
def gradle = "7.0.4"
|
||||
def gradle = "7.2.0"
|
||||
// Ref: https://kotlinlang.org/releases.html
|
||||
def kotlin = "1.6.0"
|
||||
def kotlinCoroutines = "1.6.0"
|
||||
def dagger = "2.40.5"
|
||||
def kotlin = "1.6.21"
|
||||
def kotlinCoroutines = "1.6.1"
|
||||
def dagger = "2.42"
|
||||
def retrofit = "2.9.0"
|
||||
def arrow = "0.8.2"
|
||||
def markwon = "4.6.2"
|
||||
def moshi = "1.13.0"
|
||||
def lifecycle = "2.4.0"
|
||||
def lifecycle = "2.4.1"
|
||||
def flowBinding = "1.2.0"
|
||||
def epoxy = "4.6.2"
|
||||
def mavericks = "2.5.0"
|
||||
def glide = "4.12.0"
|
||||
def mavericks = "2.6.1"
|
||||
def glide = "4.13.2"
|
||||
def bigImageViewer = "1.8.1"
|
||||
def jjwt = "0.11.2"
|
||||
def vanniktechEmoji = "0.8.0"
|
||||
def jjwt = "0.11.5"
|
||||
def vanniktechEmoji = "0.9.0"
|
||||
|
||||
// Testing
|
||||
def mockk = "1.12.1"
|
||||
def mockk = "1.12.4"
|
||||
def espresso = "3.4.0"
|
||||
def androidxTest = "1.4.0"
|
||||
def androidxOrchestrator = "1.4.1"
|
||||
@ -45,15 +45,15 @@ ext.libs = [
|
||||
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
|
||||
],
|
||||
androidx : [
|
||||
'appCompat' : "androidx.appcompat:appcompat:1.4.0",
|
||||
'appCompat' : "androidx.appcompat:appcompat:1.4.1",
|
||||
'core' : "androidx.core:core-ktx:1.7.0",
|
||||
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
|
||||
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3",
|
||||
'fragmentKtx' : "androidx.fragment:fragment-ktx:1.4.0",
|
||||
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.2",
|
||||
'fragmentKtx' : "androidx.fragment:fragment-ktx:1.4.1",
|
||||
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.3",
|
||||
'work' : "androidx.work:work-runtime-ktx:2.7.1",
|
||||
'autoFill' : "androidx.autofill:autofill:1.1.0",
|
||||
'preferenceKtx' : "androidx.preference:preference-ktx:1.1.1",
|
||||
'preferenceKtx' : "androidx.preference:preference-ktx:1.2.0",
|
||||
'junit' : "androidx.test.ext:junit:1.1.3",
|
||||
'lifecycleCommon' : "androidx.lifecycle:lifecycle-common:$lifecycle",
|
||||
'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle",
|
||||
@ -72,7 +72,7 @@ ext.libs = [
|
||||
'espressoIntents' : "androidx.test.espresso:espresso-intents:$espresso"
|
||||
],
|
||||
google : [
|
||||
'material' : "com.google.android.material:material:1.5.0"
|
||||
'material' : "com.google.android.material:material:1.6.0"
|
||||
],
|
||||
dagger : [
|
||||
'dagger' : "com.google.dagger:dagger:$dagger",
|
||||
|
@ -123,6 +123,7 @@ ext.groups = [
|
||||
'io.github.detekt.sarif4k',
|
||||
'io.github.microutils',
|
||||
'io.github.reactivecircus.flowbinding',
|
||||
'io.gitlab.arturbosch.detekt',
|
||||
'io.grpc',
|
||||
'io.jsonwebtoken',
|
||||
'io.kindedj',
|
||||
@ -195,6 +196,7 @@ ext.groups = [
|
||||
'org.testng',
|
||||
'org.threeten',
|
||||
'org.webjars',
|
||||
'org.yaml',
|
||||
'ru.noties',
|
||||
'xerces',
|
||||
'xml-apis',
|
||||
|
@ -37,9 +37,9 @@ Wording: "We've sent you an email to verify your address. Please follow the inst
|
||||
}
|
||||
```
|
||||
|
||||
## User receive an e-mail
|
||||
## User receives an e-mail
|
||||
|
||||
> [homeserver.org] Validate your email
|
||||
> `homeserver.org` Validate your email
|
||||
>
|
||||
> A request to add an email address to your Matrix account has been received. If this was you, please click the link below to confirm adding this email:
|
||||
https://homeserver.org/_matrix/client/unstable/add_threepid/email/submit_token?token=WUnEhQAmJrXupdEbXgdWvnVIKaGYZFsU&client_secret=TixzvOnw7nLEUdiQEmkHzkXKrY4HhiGh&sid=bxyDHuJKsdkjMlTJ
|
||||
|
@ -43,14 +43,17 @@ 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 .`:
|
||||
Alternatively, to install the latest Synapse release package (and not a cloned branch) you can run the following instead of `git clone` and `pip install -e .`:
|
||||
|
||||
```bash
|
||||
pip install matrix-synapse
|
||||
```
|
||||
|
||||
On your first run, you will want to stop the demo and edit the config to correct the `public_baseurl` to http://10.0.2.2:8080 and restart the server.
|
||||
|
||||
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
|
||||
@ -87,6 +90,18 @@ You'll need python3 to be able to run synapse
|
||||
|
||||
Try on the Emulator browser to open "http://10.0.2.2:8080". You should see the "Synapse is running" message.
|
||||
|
||||
### Tests partially run but some fail with "Unable to contact localhost:8080"
|
||||
|
||||
This is because the `public_baseurl` of synapse is not consistent with the endpoint that the tests are connecting to.
|
||||
|
||||
Ensure you have the following configuration in `demo/etc/8080.config`.
|
||||
|
||||
```
|
||||
public_baseurl: http://10.0.2.2:8080/
|
||||
```
|
||||
|
||||
After changing this you will need to restart synapse using `demo/stop.sh` and `demo/start.sh` to load the new configuration.
|
||||
|
||||
### virtualenv command fails
|
||||
|
||||
You can try using
|
||||
|
@ -30,6 +30,19 @@ In any case, it is better to explicitly declare in the description why the PR is
|
||||
|
||||
Also, draft PR should not stay indefinitely in this state. It may be removed if it is the case and the submitter does not update it after a few days.
|
||||
|
||||
##### Base branch
|
||||
|
||||
The `develop` branch is generally the base branch for every PRs.
|
||||
|
||||
Exceptions can occur:
|
||||
|
||||
- if a feature implementation is split into multiple PRs. We can have a chain of PRs in this case. PR can be merged one by one on develop, and GitHub change the target branch to `develop` for the next PR automatically.
|
||||
- we want to merge a PR from the community, but there is still work to do, and the PR is not updated by the submitter. First, we can kindly ask the submitter if they will update their PR, by commenting it. If there is no answer after a few days (including a week-end), we can create a new branch, push it, and change the target branch of the PR to this new branch. The PR can then be merged, and we can add more commits to fix the issues. After that a new PR can be created with `develop` as a target branch.
|
||||
|
||||
**Important notice 1:** Releases are created from the `develop` branch. So `develop` branch should always contain a "releasable" source code. So when a feature is being implemented with several PRs, it has to be disabled by default (using a feature flag for instance), until the feature is fully implemented. A last PR to enable the feature can then be created.
|
||||
|
||||
**Important notice 2:** Database migration: some developers and some people from the community are using the nightly build from `develop`. Multiple database migrations should be properly handled for them. This is OK to have multiple migrations between 2 releases, this is not OK to add steps to the pending database migration on `develop`. So for instance `develop` users will migrate from version 11 to version 12, then 13, then 14, and `main` users will do all those steps after they get the app upgrade.
|
||||
|
||||
##### PR Review Assignment
|
||||
|
||||
We use automatic assignment for PR reviews. A PR is automatically routed by GitHub to 2 team members using the round robin algorithm. The process is the following:
|
||||
@ -103,7 +116,7 @@ Review such PR is the same recipe than for PR from Dependabot
|
||||
##### Sync analytics plan
|
||||
|
||||
This tools imports any update in the analytics plan. See instruction in the PR itself to handle it.
|
||||
More info can be found in the file [analytics.md]
|
||||
More info can be found in the file [analytics.md](./analytics.md)
|
||||
|
||||
## Reviewing PR
|
||||
|
||||
@ -234,4 +247,4 @@ Also "Resolve conversation" should probably be hit by the creator of the convers
|
||||
|
||||
PR submitter is responsible of the incoming change. PR reviewers who approved the PR take a part of responsibility on the code which will land to develop, and then be used by our users, and the user of our forks.
|
||||
|
||||
That said, bug may still be merged on `develop`, this is still acceptable of course. In this case, please make sure an issue is created and correctly labelled. Ideally, such issues should be fixed before the next release candidate, i.e. with a higher priority. But as we release the application every 10 working days, it can be hard to fix every bugs. That's why PR should be fully tested and reviewed before being merge and we should never comment code review remark with "will be handled later", or similar comments.
|
||||
That said, bug may still be merged on `develop`, this is still acceptable of course. In this case, please make sure an issue is created and correctly labelled. Ideally, such issues should be fixed before the next release candidate, i.e. with a higher priority. But as we release the application every 10 working days, it can be hard to fix every bugs. That's why PR should be fully tested and reviewed before being merge and we should never comment code review remark with "will be handled later", or similar comments.
|
||||
|
@ -176,4 +176,4 @@ class SettingsAdvancedRobot {
|
||||
clickOn(R.string.settings_developer_mode_summary)
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
@ -44,7 +44,7 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
freeCompilerArgs += [
|
||||
"-Xopt-in=kotlin.RequiresOptIn"
|
||||
"-opt-in=kotlin.RequiresOptIn"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -52,4 +52,4 @@ android {
|
||||
dependencies {
|
||||
implementation libs.androidx.appCompat
|
||||
implementation libs.jetbrains.coroutinesAndroid
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
package im.vector.lib.core.utils.epoxy.charsequence
|
||||
|
||||
/**
|
||||
* Wrapper for a CharSequence, which support mutation of the CharSequence, which can happen during rendering
|
||||
* Wrapper for a CharSequence, which support mutation of the CharSequence, which can happen during rendering.
|
||||
*/
|
||||
class EpoxyCharSequence(val charSequence: CharSequence) {
|
||||
private val hash = charSequence.toString().hashCode()
|
||||
|
@ -17,6 +17,6 @@
|
||||
package im.vector.lib.core.utils.epoxy.charsequence
|
||||
|
||||
/**
|
||||
* Extensions to wrap CharSequence to EpoxyCharSequence
|
||||
* Extensions to wrap CharSequence to EpoxyCharSequence.
|
||||
*/
|
||||
fun CharSequence.toEpoxyCharSequence() = EpoxyCharSequence(this)
|
||||
|
@ -6,8 +6,14 @@ apply plugin: 'com.jakewharton.butterknife'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
maven { url 'https://repo1.maven.org/maven2' }
|
||||
// Do not use `google()`, it prevents Dependabot from working properly
|
||||
maven {
|
||||
url 'https://maven.google.com'
|
||||
}
|
||||
// Do not use `mavenCentral()`, it prevents Dependabot from working properly
|
||||
maven {
|
||||
url 'https://repo1.maven.org/maven2'
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
|
||||
|
@ -22,7 +22,7 @@ import im.vector.lib.multipicker.entity.MultiPickerAudioType
|
||||
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
||||
|
||||
/**
|
||||
* Audio file picker implementation
|
||||
* Audio file picker implementation.
|
||||
*/
|
||||
class AudioPicker : Picker<MultiPickerAudioType>() {
|
||||
|
||||
|
@ -28,12 +28,12 @@ import im.vector.lib.multipicker.utils.createTemporaryMediaFile
|
||||
import im.vector.lib.multipicker.utils.toMultiPickerImageType
|
||||
|
||||
/**
|
||||
* Implementation of taking a photo with Camera
|
||||
* Implementation of taking a photo with Camera.
|
||||
*/
|
||||
class CameraPicker {
|
||||
|
||||
/**
|
||||
* Start camera by using a ActivityResultLauncher
|
||||
* Start camera by using a ActivityResultLauncher.
|
||||
* @return Uri of taken photo or null if the operation is cancelled.
|
||||
*/
|
||||
fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher<Intent>): Uri {
|
||||
|
@ -28,12 +28,12 @@ import im.vector.lib.multipicker.utils.createTemporaryMediaFile
|
||||
import im.vector.lib.multipicker.utils.toMultiPickerVideoType
|
||||
|
||||
/**
|
||||
* Implementation of taking a video with Camera
|
||||
* Implementation of taking a video with Camera.
|
||||
*/
|
||||
class CameraVideoPicker {
|
||||
|
||||
/**
|
||||
* Start camera by using a ActivityResultLauncher
|
||||
* Start camera by using a ActivityResultLauncher.
|
||||
* @return Uri of taken photo or null if the operation is cancelled.
|
||||
*/
|
||||
fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher<Intent>): Uri {
|
||||
|
@ -26,7 +26,7 @@ import im.vector.lib.multipicker.entity.MultiPickerContactType
|
||||
import im.vector.lib.multipicker.utils.getColumnIndexOrNull
|
||||
|
||||
/**
|
||||
* Contact Picker implementation
|
||||
* Contact Picker implementation.
|
||||
*/
|
||||
class ContactPicker : Picker<MultiPickerContactType>() {
|
||||
|
||||
|
@ -32,7 +32,7 @@ import im.vector.lib.multipicker.utils.toMultiPickerImageType
|
||||
import im.vector.lib.multipicker.utils.toMultiPickerVideoType
|
||||
|
||||
/**
|
||||
* Implementation of selecting any type of files
|
||||
* Implementation of selecting any type of files.
|
||||
*/
|
||||
class FilePicker : Picker<MultiPickerBaseType>() {
|
||||
|
||||
|
@ -22,7 +22,7 @@ import im.vector.lib.multipicker.entity.MultiPickerImageType
|
||||
import im.vector.lib.multipicker.utils.toMultiPickerImageType
|
||||
|
||||
/**
|
||||
* Image Picker implementation
|
||||
* Image Picker implementation.
|
||||
*/
|
||||
class ImagePicker : Picker<MultiPickerImageType>() {
|
||||
|
||||
|
@ -24,7 +24,7 @@ import im.vector.lib.multipicker.utils.toMultiPickerImageType
|
||||
import im.vector.lib.multipicker.utils.toMultiPickerVideoType
|
||||
|
||||
/**
|
||||
* Image/Video Picker implementation
|
||||
* Image/Video Picker implementation.
|
||||
*/
|
||||
class MediaPicker : Picker<MultiPickerBaseMediaType>() {
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
package im.vector.lib.multipicker
|
||||
|
||||
class MultiPicker<T> {
|
||||
class MultiPicker<T> private constructor() {
|
||||
|
||||
companion object Type {
|
||||
val IMAGE by lazy { MultiPicker<ImagePicker>() }
|
||||
|
@ -24,7 +24,7 @@ import android.net.Uri
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
|
||||
/**
|
||||
* Abstract class to provide all types of Pickers
|
||||
* Abstract class to provide all types of Pickers.
|
||||
*/
|
||||
abstract class Picker<T> {
|
||||
|
||||
|
@ -22,7 +22,7 @@ import im.vector.lib.multipicker.entity.MultiPickerVideoType
|
||||
import im.vector.lib.multipicker.utils.toMultiPickerVideoType
|
||||
|
||||
/**
|
||||
* Video Picker implementation
|
||||
* Video Picker implementation.
|
||||
*/
|
||||
class VideoPicker : Picker<MultiPickerVideoType>() {
|
||||
|
||||
|
@ -126,6 +126,14 @@
|
||||
<color name="vctr_presence_indicator_online_light">@color/palette_element_green</color>
|
||||
<color name="vctr_presence_indicator_online_dark">@color/palette_element_green</color>
|
||||
|
||||
<attr name="vctr_presence_indicator_busy" format="color" />
|
||||
<color name="vctr_presence_indicator_busy_light">@color/element_alert_light</color>
|
||||
<color name="vctr_presence_indicator_busy_dark">@color/element_alert_dark</color>
|
||||
|
||||
<attr name="vctr_presence_indicator_away" format="color" />
|
||||
<color name="vctr_presence_indicator_away_light">@color/palette_element_orange</color>
|
||||
<color name="vctr_presence_indicator_away_dark">@color/palette_element_orange</color>
|
||||
|
||||
<!-- Location sharing colors -->
|
||||
<attr name="vctr_live_location" format="color" />
|
||||
<color name="vctr_live_location_light">@color/palette_prune</color>
|
||||
|
@ -17,6 +17,7 @@
|
||||
<color name="palette_melon">#FF812D</color>
|
||||
|
||||
<color name="palette_element_green">#0DBD8B</color>
|
||||
<color name="palette_element_orange">#D9B072</color>
|
||||
<color name="palette_white">#FFFFFF</color>
|
||||
<color name="palette_vermilion">#FF5B55</color>
|
||||
<!-- (unused) -->
|
||||
|
@ -44,6 +44,8 @@
|
||||
<!-- Presence Indicator colors -->
|
||||
<item name="vctr_presence_indicator_offline">@color/vctr_presence_indicator_offline_dark</item>
|
||||
<item name="vctr_presence_indicator_online">@color/vctr_presence_indicator_online_dark</item>
|
||||
<item name="vctr_presence_indicator_busy">@color/vctr_presence_indicator_busy_dark</item>
|
||||
<item name="vctr_presence_indicator_away">@color/vctr_presence_indicator_away_dark</item>
|
||||
|
||||
<!-- Some aliases -->
|
||||
<item name="vctr_header_background">?vctr_system</item>
|
||||
|
@ -44,6 +44,8 @@
|
||||
<!-- Presence Indicator colors -->
|
||||
<item name="vctr_presence_indicator_offline">@color/vctr_presence_indicator_offline_light</item>
|
||||
<item name="vctr_presence_indicator_online">@color/vctr_presence_indicator_online_light</item>
|
||||
<item name="vctr_presence_indicator_busy">@color/vctr_presence_indicator_busy_light</item>
|
||||
<item name="vctr_presence_indicator_away">@color/vctr_presence_indicator_away_light</item>
|
||||
|
||||
<!-- Some aliases -->
|
||||
<item name="vctr_header_background">?vctr_system</item>
|
||||
|
@ -7,7 +7,10 @@ apply plugin: "org.jetbrains.dokka"
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url 'https://repo1.maven.org/maven2' }
|
||||
// Do not use `mavenCentral()`, it prevents Dependabot from working properly
|
||||
maven {
|
||||
url 'https://repo1.maven.org/maven2'
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath "io.realm:realm-gradle-plugin:10.9.0"
|
||||
@ -18,7 +21,7 @@ dokkaHtml {
|
||||
dokkaSourceSets {
|
||||
configureEach {
|
||||
// Emit warnings about not documented members.
|
||||
reportUndocumented.set(true)
|
||||
// reportUndocumented.set(true)
|
||||
// Suppress legacy Riot's packages.
|
||||
perPackageOption {
|
||||
matchingRegex.set("org.matrix.android.sdk.internal.legacy.riot")
|
||||
@ -98,6 +101,9 @@ android {
|
||||
freeCompilerArgs += [
|
||||
// Disabled for now, there are too many errors. Could be handled in another dedicated PR
|
||||
// '-Xexplicit-api=strict', // or warning
|
||||
"-opt-in=kotlin.RequiresOptIn",
|
||||
// Opt in for kotlinx.coroutines.FlowPreview
|
||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||
]
|
||||
}
|
||||
|
||||
@ -175,7 +181,7 @@ dependencies {
|
||||
implementation libs.arrow.instances
|
||||
|
||||
// olm lib is now hosted in MavenCentral
|
||||
implementation 'org.matrix.android:olm-sdk:3.2.10'
|
||||
implementation 'org.matrix.android:olm-sdk:3.2.11'
|
||||
|
||||
// DI
|
||||
implementation libs.dagger.dagger
|
||||
|
@ -1,5 +1,8 @@
|
||||
# Module matrix-sdk-android
|
||||
|
||||
<!-- Note: the line below will appear only when the documentation is generated from Element-Android project, and not when it's generated from the SDK project -->
|
||||
**Note**: You are viewing the nightly documentation of the Android Matrix SDK library. The documentation of the released library can be found here: [https://matrix-org.github.io/matrix-android-sdk2/](https://matrix-org.github.io/matrix-android-sdk2/)
|
||||
|
||||
## Welcome to the matrix-sdk-android documentation!
|
||||
|
||||
This pages list the complete API that this SDK is exposing to a client application.
|
||||
@ -8,11 +11,11 @@ This pages list the complete API that this SDK is exposing to a client applicati
|
||||
|
||||
A few entry points:
|
||||
|
||||
- **Matrix**: The app will have to create and manage a Matrix object.
|
||||
- From this **Matrix** object, you will be able to get various services, including the **AuthenticationService**.
|
||||
- With this **AuthenticationService** you will be able to get an existing **Session**, or create one using a **LoginWizard** or a **RegistrationWizard**, which will finally give you a **Session**.
|
||||
- From the **Session**, you will be able to retrieve many Services, including the **RoomService**.
|
||||
- From the **RoomService**, you will be able to list the rooms, create a **Room**, and get a specific **Room**.
|
||||
- And from a **Room**, you will be able to do many things, including get a **Timeline**, send messages, etc.
|
||||
- **[Matrix](org.matrix.android.sdk.api.Matrix)**: The app will have to create and manage a **[Matrix](org.matrix.android.sdk.api.Matrix)** object.
|
||||
- From this **[Matrix](org.matrix.android.sdk.api.Matrix)** object, you will be able to get various services, including the **[AuthenticationService](org.matrix.android.sdk.api.auth.AuthenticationService)**.
|
||||
- With this **[AuthenticationService](org.matrix.android.sdk.api.auth.AuthenticationService)** you will be able to get an existing **[Session](org.matrix.android.sdk.api.session.Session)**, or create one using a **[LoginWizard](org.matrix.android.sdk.api.auth.login.LoginWizard)** or a **[RegistrationWizard](org.matrix.android.sdk.api.auth.registration.RegistrationWizard)**, which will finally give you a **[Session](org.matrix.android.sdk.api.session.Session)**.
|
||||
- From the **[Session](org.matrix.android.sdk.api.session.Session)**, you will be able to retrieve many Services, including the **[RoomService](org.matrix.android.sdk.api.session.room.RoomService)**.
|
||||
- From the **[RoomService](org.matrix.android.sdk.api.session.room.RoomService)**, you will be able to list the rooms, create a **[Room](org.matrix.android.sdk.api.session.room.Room)**, and get a specific **[Room](org.matrix.android.sdk.api.session.room.Room)**.
|
||||
- And from a **[Room](org.matrix.android.sdk.api.session.room.Room)**, you will be able to do many things, including get a **[Timeline](org.matrix.android.sdk.api.session.room.timeline.Timeline)**, send messages, etc.
|
||||
|
||||
Please read the whole documentation to learn more!
|
||||
|
@ -63,8 +63,9 @@ class CommonTestHelper(context: Context) {
|
||||
fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
|
||||
|
||||
init {
|
||||
var _matrix: TestMatrix? = null
|
||||
UiThreadStatement.runOnUiThread {
|
||||
TestMatrix.initialize(
|
||||
_matrix = TestMatrix(
|
||||
context,
|
||||
MatrixConfiguration(
|
||||
applicationFlavor = "TestFlavor",
|
||||
@ -72,7 +73,7 @@ class CommonTestHelper(context: Context) {
|
||||
)
|
||||
)
|
||||
}
|
||||
matrix = TestMatrix.getInstance()
|
||||
matrix = _matrix!!
|
||||
}
|
||||
|
||||
fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session {
|
||||
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.common
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Observer
|
||||
import org.amshove.kluent.fail
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
@ -31,8 +32,16 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
|
||||
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
@ -40,13 +49,19 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner
|
||||
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.awaitCallback
|
||||
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
||||
import java.util.UUID
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
@ -188,17 +203,49 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
// Alice sends a message
|
||||
testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1)
|
||||
testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1).first().eventId.let { sentEventId ->
|
||||
// ensure bob got it
|
||||
ensureEventReceived(aliceRoomId, sentEventId, bobSession, true)
|
||||
}
|
||||
|
||||
// Bob send 3 messages
|
||||
testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1)
|
||||
testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1)
|
||||
testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1)
|
||||
testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1).first().eventId.let { sentEventId ->
|
||||
// ensure alice got it
|
||||
ensureEventReceived(aliceRoomId, sentEventId, aliceSession, true)
|
||||
}
|
||||
|
||||
testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1).first().eventId.let { sentEventId ->
|
||||
// ensure alice got it
|
||||
ensureEventReceived(aliceRoomId, sentEventId, aliceSession, true)
|
||||
}
|
||||
testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1).first().eventId.let { sentEventId ->
|
||||
// ensure alice got it
|
||||
ensureEventReceived(aliceRoomId, sentEventId, aliceSession, true)
|
||||
}
|
||||
|
||||
// Alice sends a message
|
||||
testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1)
|
||||
testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1).first().eventId.let { sentEventId ->
|
||||
// ensure bob got it
|
||||
ensureEventReceived(aliceRoomId, sentEventId, bobSession, true)
|
||||
}
|
||||
return cryptoTestData
|
||||
}
|
||||
|
||||
private fun ensureEventReceived(roomId: String, eventId: String, session: Session, andCanDecrypt: Boolean) {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timeLineEvent = session.getRoom(roomId)?.timelineService()?.getTimelineEvent(eventId)
|
||||
if (andCanDecrypt) {
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
} else {
|
||||
timeLineEvent != null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkEncryptedEvent(event: Event, roomId: String, clearMessage: String, senderSession: Session) {
|
||||
assertEquals(EventType.ENCRYPTED, event.type)
|
||||
assertNotNull(event.content)
|
||||
@ -296,17 +343,104 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize cross-signing, set up megolm backup and save all in 4S
|
||||
*/
|
||||
fun bootstrapSecurity(session: Session) {
|
||||
initializeCrossSigning(session)
|
||||
val ssssService = session.sharedSecretStorageService()
|
||||
testHelper.runBlockingTest {
|
||||
val keyInfo = ssssService.generateKey(
|
||||
UUID.randomUUID().toString(),
|
||||
null,
|
||||
"ssss_key",
|
||||
EmptyKeySigner()
|
||||
)
|
||||
ssssService.setDefaultKey(keyInfo.keyId)
|
||||
|
||||
ssssService.storeSecret(
|
||||
MASTER_KEY_SSSS_NAME,
|
||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master!!,
|
||||
listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec))
|
||||
)
|
||||
|
||||
ssssService.storeSecret(
|
||||
SELF_SIGNING_KEY_SSSS_NAME,
|
||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned!!,
|
||||
listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec))
|
||||
)
|
||||
|
||||
ssssService.storeSecret(
|
||||
USER_SIGNING_KEY_SSSS_NAME,
|
||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user!!,
|
||||
listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec))
|
||||
)
|
||||
|
||||
// set up megolm backup
|
||||
val creationInfo = awaitCallback<MegolmBackupCreationInfo> {
|
||||
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
val version = awaitCallback<KeysVersion> {
|
||||
session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
|
||||
}
|
||||
// Save it for gossiping
|
||||
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
|
||||
|
||||
extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret ->
|
||||
ssssService.storeSecret(
|
||||
KEYBACKUP_SECRET_SSSS_NAME,
|
||||
secret,
|
||||
listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun verifySASCrossSign(alice: Session, bob: Session, roomId: String) {
|
||||
assertTrue(alice.cryptoService().crossSigningService().canCrossSign())
|
||||
assertTrue(bob.cryptoService().crossSigningService().canCrossSign())
|
||||
|
||||
val requestID = UUID.randomUUID().toString()
|
||||
val aliceVerificationService = alice.cryptoService().verificationService()
|
||||
val bobVerificationService = bob.cryptoService().verificationService()
|
||||
|
||||
val localId = UUID.randomUUID().toString()
|
||||
aliceVerificationService.requestKeyVerificationInDMs(
|
||||
localId = localId,
|
||||
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
otherUserId = bob.myUserId,
|
||||
roomId = roomId
|
||||
).transactionId
|
||||
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull {
|
||||
it.requestInfo?.fromDevice == alice.sessionParams.deviceId
|
||||
} != null
|
||||
}
|
||||
}
|
||||
val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first {
|
||||
it.requestInfo?.fromDevice == alice.sessionParams.deviceId
|
||||
}
|
||||
bobVerificationService.readyPendingVerification(listOf(VerificationMethod.SAS), alice.myUserId, incomingRequest.transactionId!!)
|
||||
|
||||
var requestID: String? = null
|
||||
// wait for it to be readied
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
val outgoingRequest = aliceVerificationService.getExistingVerificationRequests(bob.myUserId)
|
||||
.firstOrNull { it.localId == localId }
|
||||
if (outgoingRequest?.isReady == true) {
|
||||
requestID = outgoingRequest.transactionId!!
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aliceVerificationService.beginKeyVerificationInDMs(
|
||||
VerificationMethod.SAS,
|
||||
requestID,
|
||||
requestID!!,
|
||||
roomId,
|
||||
bob.myUserId,
|
||||
bob.sessionParams.credentials.deviceId!!
|
||||
@ -316,23 +450,9 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
||||
var alicePovTx: OutgoingSasVerificationTransaction? = null
|
||||
var bobPovTx: IncomingSasVerificationTransaction? = null
|
||||
|
||||
// wait for alice to get the ready
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||
bobPovTx?.performAccept()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? OutgoingSasVerificationTransaction
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID!!) as? OutgoingSasVerificationTransaction
|
||||
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
|
||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
@ -340,7 +460,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
||||
// wait for alice to get the ready
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID!!) as? IncomingSasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||
bobPovTx?.performAccept()
|
||||
@ -392,4 +512,50 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
|
||||
|
||||
return CryptoTestData(roomId, sessions)
|
||||
}
|
||||
|
||||
fun ensureCanDecrypt(sentEventIds: List<String>, session: Session, e2eRoomID: String, messagesText: List<String>) {
|
||||
sentEventIds.forEachIndexed { index, sentEventId ->
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
session.cryptoService().decryptEvent(event, "").let { result ->
|
||||
event.mxDecryptionResult = OlmDecryptionResult(
|
||||
payload = result.clearEvent,
|
||||
senderKey = result.senderCurve25519Key,
|
||||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||
)
|
||||
}
|
||||
} catch (error: MXCryptoError) {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
Log.v("TEST", "ensureCanDecrypt ${event.getClearType()} is ${event.getClearContent()}")
|
||||
event.getClearType() == EventType.MESSAGE &&
|
||||
messagesText[index] == event.getClearContent()?.toModel<MessageContent>()?.body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ensureCannotDecrypt(sentEventIds: List<String>, session: Session, e2eRoomID: String, expectedError: MXCryptoError.ErrorType? = null) {
|
||||
sentEventIds.forEach { sentEventId ->
|
||||
val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
session.cryptoService().decryptEvent(event, "")
|
||||
fail("Should not be able to decrypt event")
|
||||
} catch (error: MXCryptoError) {
|
||||
val errorType = (error as? MXCryptoError.Base)?.errorType
|
||||
if (expectedError == null) {
|
||||
assertNotNull(errorType)
|
||||
} else {
|
||||
assertEquals("Unexpected reason", expectedError, errorType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,13 +38,12 @@ import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
||||
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This mimics the Matrix class but using TestMatrixComponent internally instead of regular MatrixComponent.
|
||||
*/
|
||||
internal class TestMatrix constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
internal class TestMatrix(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
|
||||
@Inject internal lateinit var legacySessionImporter: LegacySessionImporter
|
||||
@Inject internal lateinit var authenticationService: AuthenticationService
|
||||
@ -60,13 +59,14 @@ internal class TestMatrix constructor(context: Context, matrixConfiguration: Mat
|
||||
private val uiHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
init {
|
||||
Monarchy.init(context)
|
||||
DaggerTestMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
|
||||
val appContext = context.applicationContext
|
||||
Monarchy.init(appContext)
|
||||
DaggerTestMatrixComponent.factory().create(appContext, matrixConfiguration).inject(this)
|
||||
val configuration = Configuration.Builder()
|
||||
.setExecutor(Executors.newCachedThreadPool())
|
||||
.setWorkerFactory(matrixWorkerFactory)
|
||||
.build()
|
||||
WorkManager.initialize(context, configuration)
|
||||
WorkManager.initialize(appContext, configuration)
|
||||
uiHandler.post {
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||
}
|
||||
@ -95,23 +95,6 @@ internal class TestMatrix constructor(context: Context, matrixConfiguration: Mat
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private lateinit var instance: TestMatrix
|
||||
private val isInit = AtomicBoolean(false)
|
||||
|
||||
fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
if (isInit.compareAndSet(false, true)) {
|
||||
instance = TestMatrix(context.applicationContext, matrixConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstance(): TestMatrix {
|
||||
if (isInit.compareAndSet(false, false)) {
|
||||
throw IllegalStateException("Matrix is not initialized properly. You should call TestMatrix.initialize first")
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
fun getSdkVersion(): String {
|
||||
return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||
}
|
||||
|
@ -30,13 +30,21 @@ import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.crypto.RequestResult
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
||||
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
||||
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.getRoomSummary
|
||||
@ -52,15 +60,13 @@ import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import org.matrix.android.sdk.common.TestMatrixCallback
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
@LargeTest
|
||||
class E2eeSanityTests : InstrumentedTest {
|
||||
|
||||
private val testHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
/**
|
||||
* Simple test that create an e2ee room.
|
||||
* Some new members are added, and a message is sent.
|
||||
@ -72,16 +78,24 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun testSendingE2EEMessages() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val e2eRoomID = cryptoTestData.roomId
|
||||
|
||||
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
|
||||
// we want to disable key gossiping to just check initial sending of keys
|
||||
aliceSession.cryptoService().enableKeyGossiping(false)
|
||||
cryptoTestData.secondSession?.cryptoService()?.enableKeyGossiping(false)
|
||||
|
||||
// add some more users and invite them
|
||||
val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu")
|
||||
.map {
|
||||
testHelper.createAccount(it, SessionTestParams(true))
|
||||
testHelper.createAccount(it, SessionTestParams(true)).also {
|
||||
it.cryptoService().enableKeyGossiping(false)
|
||||
}
|
||||
}
|
||||
|
||||
Log.v("#E2E TEST", "All accounts created")
|
||||
@ -95,18 +109,18 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
|
||||
// All user should accept invite
|
||||
otherAccounts.forEach { otherSession ->
|
||||
waitForAndAcceptInviteInRoom(otherSession, e2eRoomID)
|
||||
waitForAndAcceptInviteInRoom(testHelper, otherSession, e2eRoomID)
|
||||
Log.v("#E2E TEST", "${otherSession.myUserId} joined room $e2eRoomID")
|
||||
}
|
||||
|
||||
// check that alice see them as joined (not really necessary?)
|
||||
ensureMembersHaveJoined(aliceSession, otherAccounts, e2eRoomID)
|
||||
ensureMembersHaveJoined(testHelper, aliceSession, otherAccounts, e2eRoomID)
|
||||
|
||||
Log.v("#E2E TEST", "All users have joined the room")
|
||||
Log.v("#E2E TEST", "Alice is sending the message")
|
||||
|
||||
val text = "This is my message"
|
||||
val sentEventId: String? = sendMessageInRoom(aliceRoomPOV, text)
|
||||
val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text)
|
||||
// val sentEvent = testHelper.sendTextMessage(aliceRoomPOV, "Hello all", 1).first()
|
||||
Assert.assertTrue("Message should be sent", sentEventId != null)
|
||||
|
||||
@ -114,10 +128,10 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
otherAccounts.forEach { otherSession ->
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!)
|
||||
timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE
|
||||
val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,10 +150,10 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
}
|
||||
|
||||
newAccount.forEach {
|
||||
waitForAndAcceptInviteInRoom(it, e2eRoomID)
|
||||
waitForAndAcceptInviteInRoom(testHelper, it, e2eRoomID)
|
||||
}
|
||||
|
||||
ensureMembersHaveJoined(aliceSession, newAccount, e2eRoomID)
|
||||
ensureMembersHaveJoined(testHelper, aliceSession, newAccount, e2eRoomID)
|
||||
|
||||
// wait a bit
|
||||
testHelper.runBlockingTest {
|
||||
@ -164,7 +178,7 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
Log.v("#E2E TEST", "Alice sends a new message")
|
||||
|
||||
val secondMessage = "2 This is my message"
|
||||
val secondSentEventId: String? = sendMessageInRoom(aliceRoomPOV, secondMessage)
|
||||
val secondSentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, secondMessage)
|
||||
|
||||
// new members should be able to decrypt it
|
||||
newAccount.forEach { otherSession ->
|
||||
@ -188,6 +202,14 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testKeyGossipingIsEnabledByDefault() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val session = testHelper.createAccount("alice", SessionTestParams(true))
|
||||
Assert.assertTrue("Key gossiping should be enabled by default", session.cryptoService().isKeyGossipingEnabled())
|
||||
testHelper.signOutAndClose(session)
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick test for basic key backup
|
||||
* 1. Create e2e between Alice and Bob
|
||||
@ -204,6 +226,9 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun testBasicBackupImport() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
@ -227,16 +252,16 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
val sentEventIds = mutableListOf<String>()
|
||||
val messagesText = listOf("1. Hello", "2. Bob", "3. Good morning")
|
||||
messagesText.forEach { text ->
|
||||
val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
|
||||
val sentEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!.also {
|
||||
sentEventIds.add(it)
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE
|
||||
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
// we want more so let's discard the session
|
||||
@ -289,22 +314,23 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
}
|
||||
}
|
||||
// after initial sync events are not decrypted, so we have to try manually
|
||||
ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
|
||||
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
|
||||
|
||||
// Let's now import keys from backup
|
||||
|
||||
newBobSession.cryptoService().keysBackupService().let { keysBackupService ->
|
||||
newBobSession.cryptoService().keysBackupService().let { kbs ->
|
||||
val keyVersionResult = testHelper.doSync<KeysVersionResult?> {
|
||||
keysBackupService.getVersion(version.version, it)
|
||||
kbs.getVersion(version.version, it)
|
||||
}
|
||||
|
||||
val importedResult = testHelper.doSync<ImportRoomKeysResult> {
|
||||
keysBackupService.restoreKeyBackupWithPassword(
|
||||
kbs.restoreKeyBackupWithPassword(
|
||||
keyVersionResult!!,
|
||||
keyBackupPassword,
|
||||
null,
|
||||
null,
|
||||
null, it
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
@ -312,7 +338,7 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
}
|
||||
|
||||
// ensure bob can now decrypt
|
||||
ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
|
||||
cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
|
||||
|
||||
testHelper.signOutAndClose(newBobSession)
|
||||
}
|
||||
@ -323,6 +349,9 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun testSimpleGossip() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
@ -330,30 +359,28 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
|
||||
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
|
||||
|
||||
cryptoTestHelper.initializeCrossSigning(bobSession)
|
||||
|
||||
// let's send a few message to bob
|
||||
val sentEventIds = mutableListOf<String>()
|
||||
val messagesText = listOf("1. Hello", "2. Bob")
|
||||
|
||||
Log.v("#E2E TEST", "Alice sends some messages")
|
||||
messagesText.forEach { text ->
|
||||
val sentEventId = sendMessageInRoom(aliceRoomPOV, text)!!.also {
|
||||
val sentEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!.also {
|
||||
sentEventIds.add(it)
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE
|
||||
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure bob can decrypt
|
||||
ensureIsDecrypted(sentEventIds, bobSession, e2eRoomID)
|
||||
ensureIsDecrypted(testHelper, sentEventIds, bobSession, e2eRoomID)
|
||||
|
||||
// Let's now add a new bob session
|
||||
// Create a new session for bob
|
||||
@ -363,7 +390,11 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
// check that new bob can't currently decrypt
|
||||
Log.v("#E2E TEST", "check that new bob can't currently decrypt")
|
||||
|
||||
ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
|
||||
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
|
||||
// newBobSession.cryptoService().getOutgoingRoomKeyRequests()
|
||||
// .firstOrNull {
|
||||
// it.sessionId ==
|
||||
// }
|
||||
|
||||
// Try to request
|
||||
sentEventIds.forEach { sentEventId ->
|
||||
@ -372,12 +403,34 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
}
|
||||
|
||||
// wait a bit
|
||||
testHelper.runBlockingTest {
|
||||
delay(10_000)
|
||||
}
|
||||
// we need to wait a couple of syncs to let sharing occurs
|
||||
// testHelper.waitFewSyncs(newBobSession, 6)
|
||||
|
||||
// Ensure that new bob still can't decrypt (keys must have been withheld)
|
||||
ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.KEYS_WITHHELD)
|
||||
sentEventIds.forEach { sentEventId ->
|
||||
val megolmSessionId = newBobSession.getRoom(e2eRoomID)!!
|
||||
.getTimelineEvent(sentEventId)!!
|
||||
.root.content.toModel<EncryptedEventContent>()!!.sessionId
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val aliceReply = newBobSession.cryptoService().getOutgoingRoomKeyRequests()
|
||||
.first {
|
||||
it.sessionId == megolmSessionId &&
|
||||
it.roomId == e2eRoomID
|
||||
}
|
||||
.results.also {
|
||||
Log.w("##TEST", "result list is $it")
|
||||
}
|
||||
.firstOrNull { it.userId == aliceSession.myUserId }
|
||||
?.result
|
||||
aliceReply != null &&
|
||||
aliceReply is RequestResult.Failure &&
|
||||
WithHeldCode.UNAUTHORISED == aliceReply.code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
|
||||
|
||||
// Now mark new bob session as verified
|
||||
|
||||
@ -390,12 +443,7 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
newBobSession.cryptoService().reRequestRoomKeyForEvent(event)
|
||||
}
|
||||
|
||||
// wait a bit
|
||||
testHelper.runBlockingTest {
|
||||
delay(10_000)
|
||||
}
|
||||
|
||||
ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
|
||||
cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
testHelper.signOutAndClose(newBobSession)
|
||||
@ -406,6 +454,9 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun testForwardBetterKey() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSessionWithBetterKey = cryptoTestData.secondSession!!
|
||||
@ -413,35 +464,33 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
|
||||
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
|
||||
|
||||
cryptoTestHelper.initializeCrossSigning(bobSessionWithBetterKey)
|
||||
|
||||
// let's send a few message to bob
|
||||
var firstEventId: String
|
||||
val firstMessage = "1. Hello"
|
||||
|
||||
Log.v("#E2E TEST", "Alice sends some messages")
|
||||
firstMessage.let { text ->
|
||||
firstEventId = sendMessageInRoom(aliceRoomPOV, text)!!
|
||||
firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
|
||||
timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE
|
||||
val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure bob can decrypt
|
||||
ensureIsDecrypted(listOf(firstEventId), bobSessionWithBetterKey, e2eRoomID)
|
||||
ensureIsDecrypted(testHelper, listOf(firstEventId), bobSessionWithBetterKey, e2eRoomID)
|
||||
|
||||
// Let's add a new unverified session from bob
|
||||
val newBobSession = testHelper.logIntoAccount(bobSessionWithBetterKey.myUserId, SessionTestParams(true))
|
||||
|
||||
// check that new bob can't currently decrypt
|
||||
Log.v("#E2E TEST", "check that new bob can't currently decrypt")
|
||||
ensureCannotDecrypt(listOf(firstEventId), newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
|
||||
cryptoTestHelper.ensureCannotDecrypt(listOf(firstEventId), newBobSession, e2eRoomID, null)
|
||||
|
||||
// Now let alice send a new message. this time the new bob session will be able to decrypt
|
||||
var secondEventId: String
|
||||
@ -449,14 +498,14 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
|
||||
Log.v("#E2E TEST", "Alice sends some messages")
|
||||
secondMessage.let { text ->
|
||||
secondEventId = sendMessageInRoom(aliceRoomPOV, text)!!
|
||||
secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
|
||||
timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE
|
||||
val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -475,9 +524,7 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
try {
|
||||
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
||||
fail("Should not be able to decrypt event")
|
||||
} catch (error: MXCryptoError) {
|
||||
val errorType = (error as? MXCryptoError.Base)?.errorType
|
||||
assertEquals(MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX, errorType)
|
||||
} catch (_: MXCryptoError) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -499,41 +546,45 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
.markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId!!)
|
||||
|
||||
// now let new session request
|
||||
newBobSession.cryptoService().requestRoomKeyForEvent(firstEventNewBobPov.root)
|
||||
newBobSession.cryptoService().reRequestRoomKeyForEvent(firstEventNewBobPov.root)
|
||||
|
||||
// wait a bit
|
||||
testHelper.runBlockingTest {
|
||||
delay(10_000)
|
||||
}
|
||||
// We need to wait for the key request to be sent out and then a reply to be received
|
||||
|
||||
// old session should have shared the key at earliest known index now
|
||||
// we should be able to decrypt both
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
||||
} catch (error: MXCryptoError) {
|
||||
fail("Should be able to decrypt first event now $error")
|
||||
}
|
||||
}
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
|
||||
} catch (error: MXCryptoError) {
|
||||
fail("Should be able to decrypt event $error")
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
val canDecryptFirst = try {
|
||||
testHelper.runBlockingTest {
|
||||
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
||||
}
|
||||
true
|
||||
} catch (error: MXCryptoError) {
|
||||
false
|
||||
}
|
||||
val canDecryptSecond = try {
|
||||
testHelper.runBlockingTest {
|
||||
newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
|
||||
}
|
||||
true
|
||||
} catch (error: MXCryptoError) {
|
||||
false
|
||||
}
|
||||
canDecryptFirst && canDecryptSecond
|
||||
}
|
||||
}
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
testHelper.signOutAndClose(bobSessionWithBetterKey)
|
||||
testHelper.signOutAndClose(newBobSession)
|
||||
}
|
||||
|
||||
private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? {
|
||||
private fun sendMessageInRoom(testHelper: CommonTestHelper, aliceRoomPOV: Room, text: String): String? {
|
||||
aliceRoomPOV.sendService().sendTextMessage(text)
|
||||
var sentEventId: String? = null
|
||||
testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch ->
|
||||
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
|
||||
timeline.start()
|
||||
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val decryptedMsg = timeline.getSnapshot()
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
@ -552,7 +603,157 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
return sentEventId
|
||||
}
|
||||
|
||||
private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
|
||||
/**
|
||||
* Test that if a better key is forwared (lower index, it is then used)
|
||||
*/
|
||||
@Test
|
||||
fun testSelfInteractiveVerificationAndGossip() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
val aliceSession = testHelper.createAccount("alice", SessionTestParams(true))
|
||||
cryptoTestHelper.bootstrapSecurity(aliceSession)
|
||||
|
||||
// now let's create a new login from alice
|
||||
|
||||
val aliceNewSession = testHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
||||
|
||||
val oldCompleteLatch = CountDownLatch(1)
|
||||
lateinit var oldCode: String
|
||||
aliceSession.cryptoService().verificationService().addListener(object : VerificationService.Listener {
|
||||
|
||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||
val readyInfo = pr.readyInfo
|
||||
if (readyInfo != null) {
|
||||
aliceSession.cryptoService().verificationService().beginKeyVerification(
|
||||
VerificationMethod.SAS,
|
||||
aliceSession.myUserId,
|
||||
readyInfo.fromDevice,
|
||||
readyInfo.transactionId
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.d("##TEST", "exitsingPov: $tx")
|
||||
val sasTx = tx as OutgoingSasVerificationTransaction
|
||||
when (sasTx.uxState) {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
// for the test we just accept?
|
||||
oldCode = sasTx.getDecimalCodeRepresentation()
|
||||
sasTx.userHasVerifiedShortCode()
|
||||
}
|
||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
// we can release this latch?
|
||||
oldCompleteLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
val newCompleteLatch = CountDownLatch(1)
|
||||
lateinit var newCode: String
|
||||
aliceNewSession.cryptoService().verificationService().addListener(object : VerificationService.Listener {
|
||||
|
||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||
// let's ready
|
||||
aliceNewSession.cryptoService().verificationService().readyPendingVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
aliceSession.myUserId,
|
||||
pr.transactionId!!
|
||||
)
|
||||
}
|
||||
|
||||
var matchOnce = true
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.d("##TEST", "newPov: $tx")
|
||||
|
||||
val sasTx = tx as IncomingSasVerificationTransaction
|
||||
when (sasTx.uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
// no need to accept as there was a request first it will auto accept
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
if (matchOnce) {
|
||||
sasTx.userHasVerifiedShortCode()
|
||||
newCode = sasTx.getDecimalCodeRepresentation()
|
||||
matchOnce = false
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
newCompleteLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// initiate self verification
|
||||
aliceSession.cryptoService().verificationService().requestKeyVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
aliceNewSession.myUserId,
|
||||
listOf(aliceNewSession.sessionParams.deviceId!!)
|
||||
)
|
||||
testHelper.await(oldCompleteLatch)
|
||||
testHelper.await(newCompleteLatch)
|
||||
assertEquals("Decimal code should have matched", oldCode, newCode)
|
||||
|
||||
// Assert that devices are verified
|
||||
val newDeviceFromOldPov: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
|
||||
val oldDeviceFromNewPov: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
|
||||
|
||||
Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified)
|
||||
Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified)
|
||||
|
||||
// wait for secret gossiping to happen
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown()
|
||||
}
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
"MSK Private parts should be the same",
|
||||
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master,
|
||||
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master
|
||||
)
|
||||
assertEquals(
|
||||
"USK Private parts should be the same",
|
||||
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user,
|
||||
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"SSK Private parts should be the same",
|
||||
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned,
|
||||
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned
|
||||
)
|
||||
|
||||
// Let's check that we have the megolm backup key
|
||||
assertEquals(
|
||||
"Megolm key should be the same",
|
||||
aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey,
|
||||
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey
|
||||
)
|
||||
assertEquals(
|
||||
"Megolm version should be the same",
|
||||
aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version,
|
||||
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version
|
||||
)
|
||||
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
testHelper.signOutAndClose(aliceNewSession)
|
||||
}
|
||||
|
||||
private fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
otherAccounts.map {
|
||||
@ -564,7 +765,7 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String) {
|
||||
private fun waitForAndAcceptInviteInRoom(testHelper: CommonTestHelper, otherSession: Session, e2eRoomID: String) {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val roomSummary = otherSession.getRoomSummary(e2eRoomID)
|
||||
@ -576,7 +777,8 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
}
|
||||
}
|
||||
|
||||
testHelper.runBlockingTest(60_000) {
|
||||
// not sure why it's taking so long :/
|
||||
testHelper.runBlockingTest(90_000) {
|
||||
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
|
||||
try {
|
||||
otherSession.roomService().joinRoom(e2eRoomID)
|
||||
@ -594,59 +796,14 @@ class E2eeSanityTests : InstrumentedTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureCanDecrypt(sentEventIds: MutableList<String>, session: Session, e2eRoomID: String, messagesText: List<String>) {
|
||||
sentEventIds.forEachIndexed { index, sentEventId ->
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val event = session.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
session.cryptoService().decryptEvent(event, "").let { result ->
|
||||
event.mxDecryptionResult = OlmDecryptionResult(
|
||||
payload = result.clearEvent,
|
||||
senderKey = result.senderCurve25519Key,
|
||||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||
)
|
||||
}
|
||||
} catch (error: MXCryptoError) {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
event.getClearType() == EventType.MESSAGE &&
|
||||
messagesText[index] == event.getClearContent()?.toModel<MessageContent>()?.body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureIsDecrypted(sentEventIds: List<String>, session: Session, e2eRoomID: String) {
|
||||
private fun ensureIsDecrypted(testHelper: CommonTestHelper, sentEventIds: List<String>, session: Session, e2eRoomID: String) {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
sentEventIds.forEach { sentEventId ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = session.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureCannotDecrypt(sentEventIds: List<String>, newBobSession: Session, e2eRoomID: String, expectedError: MXCryptoError.ErrorType?) {
|
||||
sentEventIds.forEach { sentEventId ->
|
||||
val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
newBobSession.cryptoService().decryptEvent(event, "")
|
||||
fail("Should not be able to decrypt event")
|
||||
} catch (error: MXCryptoError) {
|
||||
val errorType = (error as? MXCryptoError.Base)?.errorType
|
||||
if (expectedError == null) {
|
||||
Assert.assertNotNull(errorType)
|
||||
} else {
|
||||
assertEquals(expectedError, errorType, "Message expected to be UISI")
|
||||
}
|
||||
val timeLineEvent = session.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
||||
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||
@ -51,10 +50,7 @@ class PreShareKeysTest : InstrumentedTest {
|
||||
// clear any outbound session
|
||||
aliceSession.cryptoService().discardOutboundSession(e2eRoomID)
|
||||
|
||||
val preShareCount = bobSession.cryptoService().getGossipingEvents().count {
|
||||
it.senderId == aliceSession.myUserId &&
|
||||
it.getClearType() == EventType.ROOM_KEY
|
||||
}
|
||||
val preShareCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys()
|
||||
|
||||
assertEquals("Bob should not have receive any key from alice at this point", 0, preShareCount)
|
||||
Log.d("#Test", "Room Key Received from alice $preShareCount")
|
||||
@ -66,23 +62,23 @@ class PreShareKeysTest : InstrumentedTest {
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val newGossipCount = bobSession.cryptoService().getGossipingEvents().count {
|
||||
it.senderId == aliceSession.myUserId &&
|
||||
it.getClearType() == EventType.ROOM_KEY
|
||||
}
|
||||
newGossipCount > preShareCount
|
||||
val newKeysCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys()
|
||||
newKeysCount > preShareCount
|
||||
}
|
||||
}
|
||||
|
||||
val latest = bobSession.cryptoService().getGossipingEvents().lastOrNull {
|
||||
it.senderId == aliceSession.myUserId &&
|
||||
it.getClearType() == EventType.ROOM_KEY
|
||||
}
|
||||
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
|
||||
val aliceOutboundSessionInRoom = aliceCryptoStore.getCurrentOutboundGroupSessionForRoom(e2eRoomID)!!.outboundGroupSession.sessionIdentifier()
|
||||
|
||||
val content = latest?.getClearContent().toModel<RoomKeyContent>()
|
||||
assertNotNull("Bob should have received and decrypted a room key event from alice", content)
|
||||
assertEquals("Wrong room", e2eRoomID, content!!.roomId)
|
||||
val megolmSessionId = content.sessionId!!
|
||||
val bobCryptoStore = (bobSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
|
||||
val aliceDeviceBobPov = bobCryptoStore.getUserDevice(aliceSession.myUserId, aliceSession.sessionParams.deviceId!!)!!
|
||||
val bobInboundForAlice = bobCryptoStore.getInboundGroupSession(aliceOutboundSessionInRoom, aliceDeviceBobPov.identityKey()!!)
|
||||
assertNotNull("Bob should have received and decrypted a room key event from alice", bobInboundForAlice)
|
||||
assertEquals("Wrong room", e2eRoomID, bobInboundForAlice!!.roomId)
|
||||
|
||||
val megolmSessionId = bobInboundForAlice.olmInboundGroupSession!!.sessionIdentifier()
|
||||
|
||||
assertEquals("Wrong session", aliceOutboundSessionInRoom, megolmSessionId)
|
||||
|
||||
val sharedIndex = aliceSession.cryptoService().getSharedWithInfo(e2eRoomID, megolmSessionId)
|
||||
.getObject(bobSession.myUserId, bobSession.sessionParams.deviceId)
|
||||
|
@ -19,59 +19,45 @@ package org.matrix.android.sdk.internal.crypto.gossiping
|
||||
import android.util.Log
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import junit.framework.TestCase.fail
|
||||
import org.amshove.kluent.internal.assertEquals
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
|
||||
import org.matrix.android.sdk.api.session.crypto.RequestResult
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState
|
||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
||||
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
@LargeTest
|
||||
class KeyShareTests : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun test_DoNotSelfShareIfNotTrusted() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
Log.v("TEST", "=======> AliceSession 1 is ${aliceSession.sessionParams.deviceId}")
|
||||
|
||||
// Create an encrypted room and add a message
|
||||
val roomId = commonTestHelper.runBlockingTest {
|
||||
@ -86,11 +72,18 @@ class KeyShareTests : InstrumentedTest {
|
||||
assertNotNull(room)
|
||||
Thread.sleep(4_000)
|
||||
assertTrue(room?.roomCryptoService()?.isEncrypted() == true)
|
||||
val sentEventId = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
|
||||
|
||||
// Open a new sessionx
|
||||
val sentEvent = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first()
|
||||
val sentEventId = sentEvent.eventId
|
||||
val sentEventText = sentEvent.getLastMessageContent()?.body
|
||||
|
||||
val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
||||
// Open a new session
|
||||
val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(false))
|
||||
// block key requesting for now as decrypt will send requests (room summary is trying to decrypt)
|
||||
aliceSession2.cryptoService().enableKeyGossiping(false)
|
||||
commonTestHelper.syncSession(aliceSession2)
|
||||
|
||||
Log.v("TEST", "=======> AliceSession 2 is ${aliceSession2.sessionParams.deviceId}")
|
||||
|
||||
val roomSecondSessionPOV = aliceSession2.getRoom(roomId)
|
||||
|
||||
@ -107,7 +100,10 @@ class KeyShareTests : InstrumentedTest {
|
||||
}
|
||||
|
||||
val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||
assertEquals("There should be no request as it's disabled", 0, outgoingRequestsBefore.size)
|
||||
|
||||
// Try to request
|
||||
aliceSession2.cryptoService().enableKeyGossiping(true)
|
||||
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
|
||||
|
||||
val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
|
||||
@ -117,10 +113,6 @@ class KeyShareTests : InstrumentedTest {
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||
.filter { req ->
|
||||
// filter out request that was known before
|
||||
!outgoingRequestsBefore.any { req.requestId == it.requestId }
|
||||
}
|
||||
.let {
|
||||
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
|
||||
outGoingRequestId = outgoing?.requestId
|
||||
@ -141,20 +133,34 @@ class KeyShareTests : InstrumentedTest {
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
// DEBUG LOGS
|
||||
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
|
||||
Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
|
||||
// aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
|
||||
// Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
|
||||
// Log.v("TEST", "=========================")
|
||||
// it.forEach { keyRequest ->
|
||||
// Log.v("TEST", "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
|
||||
// }
|
||||
// Log.v("TEST", "=========================")
|
||||
// }
|
||||
|
||||
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
|
||||
incoming != null
|
||||
}
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
// DEBUG LOGS
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequests().forEach { keyRequest ->
|
||||
Log.v("TEST", "=========================")
|
||||
it.forEach { keyRequest ->
|
||||
Log.v(
|
||||
"TEST",
|
||||
"[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId} is ${keyRequest.state}"
|
||||
)
|
||||
}
|
||||
Log.v("TEST", "requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
|
||||
Log.v("TEST", "replies -> ${keyRequest.results.joinToString { it.toString() }}")
|
||||
Log.v("TEST", "=========================")
|
||||
}
|
||||
|
||||
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
|
||||
incoming?.state == GossipingRequestState.REJECTED
|
||||
val outgoing = aliceSession2.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
|
||||
val reply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
val resultCode = (reply?.result as? RequestResult.Failure)?.code
|
||||
resultCode == WithHeldCode.UNVERIFIED
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,254 +181,301 @@ class KeyShareTests : InstrumentedTest {
|
||||
// Re request
|
||||
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
|
||||
Log.v("TEST", "Incoming request Session 1")
|
||||
Log.v("TEST", "=========================")
|
||||
it.forEach {
|
||||
Log.v("TEST", "requestId ${it.requestId}, for sessionId ${it.requestBody?.sessionId} is ${it.state}")
|
||||
}
|
||||
Log.v("TEST", "=========================")
|
||||
|
||||
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == GossipingRequestState.ACCEPTED }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thread.sleep(6_000)
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequests().let {
|
||||
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
commonTestHelper.runBlockingTest {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
fail("should have been able to decrypt")
|
||||
}
|
||||
cryptoTestHelper.ensureCanDecrypt(listOf(receivedEvent.eventId), aliceSession2, roomId, listOf(sentEventText ?: ""))
|
||||
|
||||
commonTestHelper.signOutAndClose(aliceSession)
|
||||
commonTestHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
|
||||
// See E2ESanityTest for a test regarding secret sharing
|
||||
|
||||
/**
|
||||
* Test that the sender of a message accepts to re-share to another user
|
||||
* if the key was originally shared with him
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun test_ShareSSSSSecret() {
|
||||
val aliceSession1 = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
fun test_reShareIfWasIntendedToBeShared() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
commonTestHelper.doSync<Unit> {
|
||||
aliceSession1.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(
|
||||
UserPasswordAuth(
|
||||
user = aliceSession1.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
)
|
||||
}
|
||||
}, it
|
||||
)
|
||||
}
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = testData.firstSession
|
||||
val roomFromAlice = aliceSession.getRoom(testData.roomId)!!
|
||||
val bobSession = testData.secondSession!!
|
||||
|
||||
// Also bootstrap keybackup on first session
|
||||
val creationInfo = commonTestHelper.doSync<MegolmBackupCreationInfo> {
|
||||
aliceSession1.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
val version = commonTestHelper.doSync<KeysVersion> {
|
||||
aliceSession1.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
|
||||
}
|
||||
// Save it for gossiping
|
||||
aliceSession1.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
|
||||
val sentEvent = commonTestHelper.sendTextMessage(roomFromAlice, "Hello", 1).first()
|
||||
val sentEventMegolmSession = sentEvent.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
|
||||
val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true))
|
||||
// bob should be able to decrypt
|
||||
cryptoTestHelper.ensureCanDecrypt(listOf(sentEvent.eventId), bobSession, testData.roomId, listOf(sentEvent.getLastMessageContent()?.body ?: ""))
|
||||
|
||||
val aliceVerificationService1 = aliceSession1.cryptoService().verificationService()
|
||||
val aliceVerificationService2 = aliceSession2.cryptoService().verificationService()
|
||||
|
||||
// force keys download
|
||||
commonTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true, it)
|
||||
}
|
||||
commonTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true, it)
|
||||
}
|
||||
|
||||
var session1ShortCode: String? = null
|
||||
var session2ShortCode: String? = null
|
||||
|
||||
aliceVerificationService1.addListener(object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.d("#TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}")
|
||||
if (tx is SasVerificationTransaction) {
|
||||
if (tx.state == VerificationTxState.OnStarted) {
|
||||
(tx as IncomingSasVerificationTransaction).performAccept()
|
||||
}
|
||||
if (tx.state == VerificationTxState.ShortCodeReady) {
|
||||
session1ShortCode = tx.getDecimalCodeRepresentation()
|
||||
Thread.sleep(500)
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
aliceVerificationService2.addListener(object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.d("#TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}")
|
||||
if (tx is SasVerificationTransaction) {
|
||||
if (tx.state == VerificationTxState.ShortCodeReady) {
|
||||
session2ShortCode = tx.getDecimalCodeRepresentation()
|
||||
Thread.sleep(500)
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
val txId = "m.testVerif12"
|
||||
aliceVerificationService2.beginKeyVerification(
|
||||
VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
|
||||
?: "", txId
|
||||
)
|
||||
// Let's try to request any how.
|
||||
// As it was share previously alice should accept to reshare
|
||||
bobSession.cryptoService().reRequestRoomKeyForEvent(sentEvent.root)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
|
||||
val outgoing = bobSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val aliceReply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
aliceReply != null && aliceReply.result is RequestResult.Success
|
||||
}
|
||||
}
|
||||
|
||||
assertNotNull(session1ShortCode)
|
||||
Log.d("#TEST", "session1ShortCode: $session1ShortCode")
|
||||
assertNotNull(session2ShortCode)
|
||||
Log.d("#TEST", "session2ShortCode: $session2ShortCode")
|
||||
assertEquals(session1ShortCode, session2ShortCode)
|
||||
|
||||
// SSK and USK private keys should have been shared
|
||||
|
||||
commonTestHelper.waitWithLatch(60_000) { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
Log.d("#TEST", "CAN XS :${aliceSession2.cryptoService().crossSigningService().getMyCrossSigningKeys()}")
|
||||
aliceSession2.cryptoService().crossSigningService().canCrossSign()
|
||||
}
|
||||
}
|
||||
|
||||
// Test that key backup key has been shared to
|
||||
commonTestHelper.waitWithLatch(60_000) { latch ->
|
||||
val keysBackupService = aliceSession2.cryptoService().keysBackupService()
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
Log.d("#TEST", "Recovery :${keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}")
|
||||
keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey
|
||||
}
|
||||
}
|
||||
|
||||
commonTestHelper.signOutAndClose(aliceSession1)
|
||||
commonTestHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that our own devices accept to reshare to unverified device if it was shared initialy
|
||||
* if the key was originally shared with him
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun test_ImproperKeyShareBug() {
|
||||
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
fun test_reShareToUnverifiedIfWasIntendedToBeShared() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
commonTestHelper.doSync<Unit> {
|
||||
aliceSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(
|
||||
UserPasswordAuth(
|
||||
user = aliceSession.myUserId,
|
||||
password = TestConstants.PASSWORD,
|
||||
session = flowResponse.session
|
||||
)
|
||||
)
|
||||
}
|
||||
}, it
|
||||
)
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceInARoom(true)
|
||||
val aliceSession = testData.firstSession
|
||||
val roomFromAlice = aliceSession.getRoom(testData.roomId)!!
|
||||
|
||||
val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
||||
|
||||
// we wait for alice first session to be aware of that session?
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val newSession = aliceSession.cryptoService().getUserDevices(aliceSession.myUserId)
|
||||
.firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId }
|
||||
newSession != null
|
||||
}
|
||||
}
|
||||
val sentEvent = commonTestHelper.sendTextMessage(roomFromAlice, "Hello", 1).first()
|
||||
val sentEventMegolmSession = sentEvent.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
|
||||
// Create an encrypted room and send a couple of messages
|
||||
val roomId = commonTestHelper.runBlockingTest {
|
||||
aliceSession.roomService().createRoom(
|
||||
CreateRoomParams().apply {
|
||||
visibility = RoomDirectoryVisibility.PRIVATE
|
||||
enableEncryption()
|
||||
}
|
||||
)
|
||||
// Let's try to request any how.
|
||||
// As it was share previously alice should accept to reshare
|
||||
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvent.root)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val ownDeviceReply =
|
||||
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
ownDeviceReply != null && ownDeviceReply.result is RequestResult.Success
|
||||
}
|
||||
}
|
||||
val roomAlicePov = aliceSession.getRoom(roomId)
|
||||
assertNotNull(roomAlicePov)
|
||||
Thread.sleep(1_000)
|
||||
assertTrue(roomAlicePov?.roomCryptoService()?.isEncrypted() == true)
|
||||
val secondEventId = commonTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId
|
||||
}
|
||||
|
||||
// Create bob session
|
||||
/**
|
||||
* Tests that keys reshared with own verified session are done from the earliest known index
|
||||
*/
|
||||
@Test
|
||||
fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
|
||||
commonTestHelper.doSync<Unit> {
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(
|
||||
UserPasswordAuth(
|
||||
user = bobSession.myUserId,
|
||||
password = TestConstants.PASSWORD,
|
||||
session = flowResponse.session
|
||||
)
|
||||
)
|
||||
}
|
||||
}, it
|
||||
)
|
||||
}
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = testData.firstSession
|
||||
val bobSession = testData.secondSession!!
|
||||
val roomFromBob = bobSession.getRoom(testData.roomId)!!
|
||||
|
||||
// Let alice invite bob
|
||||
commonTestHelper.runBlockingTest {
|
||||
roomAlicePov.membershipService().invite(bobSession.myUserId, null)
|
||||
}
|
||||
val sentEvents = commonTestHelper.sendTextMessage(roomFromBob, "Hello", 3)
|
||||
val sentEventMegolmSession = sentEvents.first().root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
|
||||
commonTestHelper.runBlockingTest {
|
||||
bobSession.roomService().joinRoom(roomAlicePov.roomId, null, emptyList())
|
||||
}
|
||||
// Let alice now add a new session
|
||||
val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(false))
|
||||
aliceNewSession.cryptoService().enableKeyGossiping(false)
|
||||
commonTestHelper.syncSession(aliceNewSession)
|
||||
|
||||
// we want to discard alice outbound session
|
||||
aliceSession.cryptoService().discardOutboundSession(roomAlicePov.roomId)
|
||||
|
||||
// and now resend a new message to reset index to 0
|
||||
commonTestHelper.sendTextMessage(roomAlicePov, "After", 1)
|
||||
|
||||
val roomRoomBobPov = aliceSession.getRoom(roomId)
|
||||
val beforeJoin = roomRoomBobPov!!.getTimelineEvent(secondEventId)
|
||||
|
||||
var dRes = tryOrNull {
|
||||
commonTestHelper.runBlockingTest {
|
||||
bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "")
|
||||
// we wait bob first session to be aware of that session?
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val newSession = bobSession.cryptoService().getUserDevices(aliceSession.myUserId)
|
||||
.firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId }
|
||||
newSession != null
|
||||
}
|
||||
}
|
||||
|
||||
assert(dRes == null)
|
||||
val newEvent = commonTestHelper.sendTextMessage(roomFromBob, "The New", 1).first()
|
||||
val newEventId = newEvent.eventId
|
||||
val newEventText = newEvent.getLastMessageContent()!!.body
|
||||
|
||||
// Try to re-ask the keys
|
||||
// alice should be able to decrypt the new one
|
||||
cryptoTestHelper.ensureCanDecrypt(listOf(newEventId), aliceNewSession, testData.roomId, listOf(newEventText))
|
||||
// but not the first one!
|
||||
cryptoTestHelper.ensureCannotDecrypt(sentEvents.map { it.eventId }, aliceNewSession, testData.roomId)
|
||||
|
||||
bobSession.cryptoService().reRequestRoomKeyForEvent(beforeJoin!!.root)
|
||||
// All should be using the same session id
|
||||
sentEvents.forEach {
|
||||
assertEquals(sentEventMegolmSession, it.root.content.toModel<EncryptedEventContent>()!!.sessionId)
|
||||
}
|
||||
assertEquals(sentEventMegolmSession, newEvent.root.content.toModel<EncryptedEventContent>()!!.sessionId)
|
||||
|
||||
Thread.sleep(3_000)
|
||||
// Request a first time, bob should reply with unauthorized and alice should reply with unverified
|
||||
aliceNewSession.cryptoService().enableKeyGossiping(true)
|
||||
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(newEvent.root)
|
||||
|
||||
// With the bug the first session would have improperly reshare that key :/
|
||||
dRes = tryOrNull {
|
||||
commonTestHelper.runBlockingTest {
|
||||
bobSession.cryptoService().decryptEvent(beforeJoin.root, "")
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val ownDeviceReply = outgoing?.results
|
||||
?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
val result = ownDeviceReply?.result
|
||||
Log.v("TEST", "own device result is $result")
|
||||
result != null && result is RequestResult.Failure && result.code == WithHeldCode.UNVERIFIED
|
||||
}
|
||||
}
|
||||
Log.d("#TEST", "KS: sgould not decrypt that ${beforeJoin.root.getClearContent().toModel<MessageContent>()?.body}")
|
||||
assert(dRes?.clearEvent == null)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val bobDeviceReply = outgoing?.results
|
||||
?.firstOrNull { it.userId == bobSession.myUserId && it.fromDevice == bobSession.sessionParams.deviceId }
|
||||
val result = bobDeviceReply?.result
|
||||
Log.v("TEST", "bob device result is $result")
|
||||
result != null && result is RequestResult.Success && result.chainIndex > 0
|
||||
}
|
||||
}
|
||||
|
||||
// it's a success but still can't decrypt first message
|
||||
cryptoTestHelper.ensureCannotDecrypt(sentEvents.map { it.eventId }, aliceNewSession, testData.roomId)
|
||||
|
||||
// Mark the new session as verified
|
||||
aliceSession.cryptoService()
|
||||
.verificationService()
|
||||
.markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!)
|
||||
|
||||
// Let's now try to request
|
||||
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
// DEBUG LOGS
|
||||
aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().forEach { keyRequest ->
|
||||
Log.v("TEST", "=========================")
|
||||
Log.v("TEST", "requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
|
||||
Log.v("TEST", "replies -> ${keyRequest.results.joinToString { it.toString() }}")
|
||||
Log.v("TEST", "=========================")
|
||||
}
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val ownDeviceReply =
|
||||
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
val result = ownDeviceReply?.result
|
||||
result != null && result is RequestResult.Success && result.chainIndex == 0
|
||||
}
|
||||
}
|
||||
|
||||
// now the new session should be able to decrypt all!
|
||||
cryptoTestHelper.ensureCanDecrypt(
|
||||
sentEvents.map { it.eventId },
|
||||
aliceNewSession,
|
||||
testData.roomId,
|
||||
sentEvents.map { it.getLastMessageContent()!!.body }
|
||||
)
|
||||
|
||||
// Additional test, can we check that bob replied successfully but with a ratcheted key
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val bobReply = outgoing?.results?.firstOrNull { it.userId == bobSession.myUserId }
|
||||
val result = bobReply?.result
|
||||
result != null && result is RequestResult.Success && result.chainIndex == 3
|
||||
}
|
||||
}
|
||||
|
||||
commonTestHelper.signOutAndClose(aliceNewSession)
|
||||
commonTestHelper.signOutAndClose(aliceSession)
|
||||
commonTestHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we don't cancel a request to early on first forward if the index is not good enough
|
||||
*/
|
||||
@Test
|
||||
fun test_dontCancelToEarly() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = testData.firstSession
|
||||
val bobSession = testData.secondSession!!
|
||||
val roomFromBob = bobSession.getRoom(testData.roomId)!!
|
||||
|
||||
val sentEvents = commonTestHelper.sendTextMessage(roomFromBob, "Hello", 3)
|
||||
val sentEventMegolmSession = sentEvents.first().root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
|
||||
// Let alice now add a new session
|
||||
val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
||||
|
||||
// we wait bob first session to be aware of that session?
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val newSession = bobSession.cryptoService().getUserDevices(aliceSession.myUserId)
|
||||
.firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId }
|
||||
newSession != null
|
||||
}
|
||||
}
|
||||
|
||||
val newEvent = commonTestHelper.sendTextMessage(roomFromBob, "The New", 1).first()
|
||||
val newEventId = newEvent.eventId
|
||||
val newEventText = newEvent.getLastMessageContent()!!.body
|
||||
|
||||
// alice should be able to decrypt the new one
|
||||
cryptoTestHelper.ensureCanDecrypt(listOf(newEventId), aliceNewSession, testData.roomId, listOf(newEventText))
|
||||
// but not the first one!
|
||||
cryptoTestHelper.ensureCannotDecrypt(sentEvents.map { it.eventId }, aliceNewSession, testData.roomId)
|
||||
|
||||
// All should be using the same session id
|
||||
sentEvents.forEach {
|
||||
assertEquals(sentEventMegolmSession, it.root.content.toModel<EncryptedEventContent>()!!.sessionId)
|
||||
}
|
||||
assertEquals(sentEventMegolmSession, newEvent.root.content.toModel<EncryptedEventContent>()!!.sessionId)
|
||||
|
||||
// Mark the new session as verified
|
||||
aliceSession.cryptoService()
|
||||
.verificationService()
|
||||
.markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!)
|
||||
|
||||
// /!\ Stop initial alice session syncing so that it can't reply
|
||||
aliceSession.cryptoService().enableKeyGossiping(false)
|
||||
aliceSession.stopSync()
|
||||
|
||||
// Let's now try to request
|
||||
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root)
|
||||
|
||||
// Should get a reply from bob and not from alice
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
// Log.d("#TEST", "outgoing key requests :${aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().joinToString { it.sessionId ?: "?" }}")
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val bobReply = outgoing?.results?.firstOrNull { it.userId == bobSession.myUserId }
|
||||
val result = bobReply?.result
|
||||
result != null && result is RequestResult.Success && result.chainIndex == 3
|
||||
}
|
||||
}
|
||||
|
||||
val outgoingReq = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
|
||||
assertNull("We should not have a reply from first session", outgoingReq!!.results.firstOrNull { it.fromDevice == aliceSession.sessionParams.deviceId })
|
||||
assertEquals("The request should not be canceled", OutgoingRoomKeyRequestState.SENT, outgoingReq.state)
|
||||
|
||||
// let's wake up alice
|
||||
aliceSession.cryptoService().enableKeyGossiping(true)
|
||||
aliceSession.startSync(true)
|
||||
|
||||
// We should now get a reply from first session
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val ownDeviceReply =
|
||||
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
val result = ownDeviceReply?.result
|
||||
result != null && result is RequestResult.Success && result.chainIndex == 0
|
||||
}
|
||||
}
|
||||
|
||||
// It should be in sent then cancel
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
assertEquals("The request should be canceled", OutgoingRoomKeyRequestState.SENT_THEN_CANCELED, outgoing!!.state)
|
||||
|
||||
commonTestHelper.signOutAndClose(aliceNewSession)
|
||||
commonTestHelper.signOutAndClose(aliceSession)
|
||||
commonTestHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import org.junit.Assert
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
@ -29,6 +28,7 @@ import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.crypto.RequestResult
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
||||
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||
@ -46,12 +46,11 @@ import org.matrix.android.sdk.common.TestConstants
|
||||
@LargeTest
|
||||
class WithHeldTests : InstrumentedTest {
|
||||
|
||||
private val testHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun test_WithHeldUnverifiedReason() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
// =============================
|
||||
// ARRANGE
|
||||
// =============================
|
||||
@ -69,7 +68,6 @@ class WithHeldTests : InstrumentedTest {
|
||||
val roomAlicePOV = aliceSession.getRoom(roomId)!!
|
||||
|
||||
val bobUnverifiedSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
|
||||
|
||||
// =============================
|
||||
// ACT
|
||||
// =============================
|
||||
@ -88,6 +86,7 @@ class WithHeldTests : InstrumentedTest {
|
||||
|
||||
val eventBobPOV = bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(timelineEvent.eventId)!!
|
||||
|
||||
val megolmSessionId = eventBobPOV.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
// =============================
|
||||
// ASSERT
|
||||
// =============================
|
||||
@ -103,9 +102,23 @@ class WithHeldTests : InstrumentedTest {
|
||||
val type = (failure as MXCryptoError.Base).errorType
|
||||
val technicalMessage = failure.technicalMessage
|
||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNAUTHORISED.value, technicalMessage)
|
||||
}
|
||||
|
||||
// Let's see if the reply we got from bob first session is unverified
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
bobUnverifiedSession.cryptoService().getOutgoingRoomKeyRequests()
|
||||
.firstOrNull { it.sessionId == megolmSessionId }
|
||||
?.results
|
||||
?.firstOrNull { it.fromDevice == bobSession.sessionParams.deviceId }
|
||||
?.result
|
||||
?.let {
|
||||
it as? RequestResult.Failure
|
||||
}
|
||||
?.code == WithHeldCode.UNVERIFIED
|
||||
}
|
||||
}
|
||||
// enable back sending to unverified
|
||||
aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(false)
|
||||
|
||||
@ -130,7 +143,7 @@ class WithHeldTests : InstrumentedTest {
|
||||
val type = (failure as MXCryptoError.Base).errorType
|
||||
val technicalMessage = failure.technicalMessage
|
||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNAUTHORISED.value, technicalMessage)
|
||||
}
|
||||
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
@ -139,8 +152,10 @@ class WithHeldTests : InstrumentedTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun test_WithHeldNoOlm() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = testData.firstSession
|
||||
val bobSession = testData.secondSession!!
|
||||
@ -220,8 +235,10 @@ class WithHeldTests : InstrumentedTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun test_WithHeldKeyRequest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = testData.firstSession
|
||||
val bobSession = testData.secondSession!!
|
||||
@ -267,5 +284,8 @@ class WithHeldTests : InstrumentedTest {
|
||||
wc?.code == WithHeldCode.UNAUTHORISED
|
||||
}
|
||||
}
|
||||
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
testHelper.signOutAndClose(bobSecondSession)
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
@ -37,7 +36,9 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersio
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrustSignature
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
|
||||
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
||||
@ -54,18 +55,16 @@ import java.util.concurrent.CountDownLatch
|
||||
@LargeTest
|
||||
class KeysBackupTest : InstrumentedTest {
|
||||
|
||||
private val testHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
private val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
/**
|
||||
* - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
|
||||
* - Check backup keys after having marked one as backed up
|
||||
* - Reset keys backup markers
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun roomKeysTest_testBackupStore_ok() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
// From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
|
||||
@ -104,6 +103,8 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun prepareKeysBackupVersionTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
|
||||
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
|
||||
|
||||
assertNotNull(bobSession.cryptoService().keysBackupService())
|
||||
@ -132,7 +133,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun createKeysBackupVersionTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
|
||||
cryptoTestHelper.initializeCrossSigning(bobSession)
|
||||
|
||||
val keysBackup = bobSession.cryptoService().keysBackupService()
|
||||
|
||||
@ -147,13 +152,46 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
// Create the version
|
||||
testHelper.doSync<KeysVersion> {
|
||||
val version = testHelper.doSync<KeysVersion> {
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
// Backup must be enable now
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
|
||||
// Check that it's signed with MSK
|
||||
val versionResult = testHelper.doSync<KeysVersionResult?> {
|
||||
keysBackup.getVersion(version.version, it)
|
||||
}
|
||||
val trust = testHelper.doSync<KeysBackupVersionTrust> {
|
||||
keysBackup.getKeysBackupTrust(versionResult!!, it)
|
||||
}
|
||||
|
||||
assertEquals("Should have 2 signatures", 2, trust.signatures.size)
|
||||
|
||||
trust.signatures
|
||||
.firstOrNull { it is KeysBackupVersionTrustSignature.DeviceSignature }
|
||||
.let {
|
||||
assertNotNull("Should be signed by a device", it)
|
||||
it as KeysBackupVersionTrustSignature.DeviceSignature
|
||||
}.let {
|
||||
assertEquals("Should be signed by current device", bobSession.sessionParams.deviceId, it.deviceId)
|
||||
assertTrue("Signature should be valid", it.valid)
|
||||
}
|
||||
|
||||
trust.signatures
|
||||
.firstOrNull { it is KeysBackupVersionTrustSignature.UserSignature }
|
||||
.let {
|
||||
assertNotNull("Should be signed by a user", it)
|
||||
it as KeysBackupVersionTrustSignature.UserSignature
|
||||
}.let {
|
||||
val msk = bobSession.cryptoService().crossSigningService()
|
||||
.getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey
|
||||
assertEquals("Should be signed by my msk 1", msk, it.keyId)
|
||||
assertEquals("Should be signed by my msk 2", msk, it.cryptoCrossSigningKey?.unpaddedBase64PublicKey)
|
||||
assertTrue("Signature should be valid", it.valid)
|
||||
}
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
@ -163,8 +201,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - Check the backup completes
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun backupAfterCreateKeysBackupVersionTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
keysBackupTestHelper.waitForKeybackUpBatching()
|
||||
@ -204,8 +245,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* Check that backupAllGroupSessions() returns valid data
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun backupAllGroupSessionsTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
|
||||
@ -249,8 +293,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - Compare the decrypted megolm key with the original one
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun testEncryptAndDecryptKeysBackupData() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService
|
||||
@ -293,8 +340,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - Restore must be successful
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun restoreKeysBackupTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Restore the e2e backup from the homeserver
|
||||
@ -378,8 +428,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - It must be trusted and must have with 2 signatures now
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun trustKeyBackupVersionTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Do an e2e backup to the homeserver with a recovery key
|
||||
// - And log Alice on a new device
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
@ -438,8 +491,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - It must be trusted and must have with 2 signatures now
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun trustKeyBackupVersionWithRecoveryKeyTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Do an e2e backup to the homeserver with a recovery key
|
||||
// - And log Alice on a new device
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
@ -496,8 +552,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - The backup must still be untrusted and disabled
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun trustKeyBackupVersionWithWrongRecoveryKeyTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Do an e2e backup to the homeserver with a recovery key
|
||||
// - And log Alice on a new device
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
@ -538,8 +597,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - It must be trusted and must have with 2 signatures now
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun trustKeyBackupVersionWithPasswordTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val password = "Password"
|
||||
|
||||
// - Do an e2e backup to the homeserver with a password
|
||||
@ -598,8 +660,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - The backup must still be untrusted and disabled
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun trustKeyBackupVersionWithWrongPasswordTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val password = "Password"
|
||||
val badPassword = "Bad Password"
|
||||
|
||||
@ -639,8 +704,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - It must fail
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun restoreKeysBackupWithAWrongRecoveryKeyTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Try to restore the e2e backup with a wrong recovery key
|
||||
@ -673,8 +741,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - Restore must be successful
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun testBackupWithPassword() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val password = "password"
|
||||
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
@ -730,8 +801,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - It must fail
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun restoreKeysBackupWithAWrongPasswordTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val password = "password"
|
||||
val wrongPassword = "passw0rd"
|
||||
|
||||
@ -767,8 +841,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - Restore must be successful
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val password = "password"
|
||||
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
@ -797,8 +874,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - It must fail
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Try to restore the e2e backup with a password
|
||||
@ -829,8 +909,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* - Check the returned KeysVersionResult is trusted
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun testIsKeysBackupTrusted() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Create a backup version
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
@ -855,7 +938,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertTrue(keysBackupVersionTrust.usable)
|
||||
assertEquals(1, keysBackupVersionTrust.signatures.size)
|
||||
|
||||
val signature = keysBackupVersionTrust.signatures[0]
|
||||
val signature = keysBackupVersionTrust.signatures[0] as KeysBackupVersionTrustSignature.DeviceSignature
|
||||
assertTrue(signature.valid)
|
||||
assertNotNull(signature.device)
|
||||
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
|
||||
@ -865,66 +948,6 @@ class KeysBackupTest : InstrumentedTest {
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check backup starts automatically if there is an existing and compatible backup
|
||||
* version on the homeserver.
|
||||
* - Create a backup version
|
||||
* - Restart alice session
|
||||
* -> The new alice session must back up to the same version
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() {
|
||||
// - Create a backup version
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
|
||||
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
assertTrue(keysBackup.isEnabled)
|
||||
|
||||
// - Restart alice session
|
||||
// - Log Alice on a new device
|
||||
val aliceSession2 = testHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
|
||||
val keysBackup2 = aliceSession2.cryptoService().keysBackupService()
|
||||
|
||||
val stateObserver2 = StateObserver(keysBackup2)
|
||||
|
||||
// -> The new alice session must back up to the same version
|
||||
val latch = CountDownLatch(1)
|
||||
var count = 0
|
||||
keysBackup2.addListener(object : KeysBackupStateListener {
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
// Check the backup completes
|
||||
if (newState == KeysBackupState.ReadyToBackUp) {
|
||||
count++
|
||||
|
||||
if (count == 2) {
|
||||
// Remove itself from the list of listeners
|
||||
keysBackup2.removeListener(this)
|
||||
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
testHelper.await(latch)
|
||||
|
||||
assertEquals(keyBackupCreationInfo.version, keysBackup2.currentBackupVersion)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
stateObserver2.stopAndCheckStates(null)
|
||||
testHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check WrongBackUpVersion state
|
||||
*
|
||||
@ -935,6 +958,10 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun testBackupWhenAnotherBackupWasCreated() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Create a backup version
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
@ -1005,8 +1032,11 @@ class KeysBackupTest : InstrumentedTest {
|
||||
* -> It must success
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun testBackupAfterVerifyingADevice() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Create a backup version
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
@ -1039,6 +1069,8 @@ class KeysBackupTest : InstrumentedTest {
|
||||
// - Try to backup all in aliceSession2, it must fail
|
||||
val keysBackup2 = aliceSession2.cryptoService().keysBackupService()
|
||||
|
||||
assertFalse("Backup should not be enabled", keysBackup2.isEnabled)
|
||||
|
||||
val stateObserver2 = StateObserver(keysBackup2)
|
||||
|
||||
var isSuccessful = false
|
||||
@ -1056,8 +1088,8 @@ class KeysBackupTest : InstrumentedTest {
|
||||
assertFalse(isSuccessful)
|
||||
|
||||
// Backup state must be NotTrusted
|
||||
assertEquals(KeysBackupState.NotTrusted, keysBackup2.state)
|
||||
assertFalse(keysBackup2.isEnabled)
|
||||
assertEquals("Backup state must be NotTrusted", KeysBackupState.NotTrusted, keysBackup2.state)
|
||||
assertFalse("Backup should not be enabled", keysBackup2.isEnabled)
|
||||
|
||||
// - Validate the old device from the new one
|
||||
aliceSession2.cryptoService().setDeviceVerification(
|
||||
@ -1103,6 +1135,10 @@ class KeysBackupTest : InstrumentedTest {
|
||||
*/
|
||||
@Test
|
||||
fun deleteKeysBackupTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Create a backup version
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
|
@ -106,14 +106,14 @@ internal class KeysBackupTestHelper(
|
||||
|
||||
Assert.assertNotNull(megolmBackupCreationInfo)
|
||||
|
||||
Assert.assertFalse(keysBackup.isEnabled)
|
||||
Assert.assertFalse("Key backup should not be enabled before creation", keysBackup.isEnabled)
|
||||
|
||||
// Create the version
|
||||
val keysVersion = testHelper.doSync<KeysVersion> {
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
Assert.assertNotNull(keysVersion.version)
|
||||
Assert.assertNotNull("Key backup version should not be null", keysVersion.version)
|
||||
|
||||
// Backup must be enable now
|
||||
Assert.assertTrue(keysBackup.isEnabled)
|
||||
|
@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.verification.qrcode
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.amshove.kluent.shouldBe
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
@ -40,7 +39,6 @@ import kotlin.coroutines.resume
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
@Ignore("This test is flaky ; see issue #5449")
|
||||
class VerificationTest : InstrumentedTest {
|
||||
|
||||
data class ExpectedResult(
|
||||
|
@ -38,15 +38,18 @@ import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
||||
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This is the main entry point to the matrix sdk.
|
||||
* <br/>
|
||||
* See [Companion.createInstance] to create an instance. The app should create and manage the instance itself.
|
||||
*
|
||||
* The constructor creates a new instance of Matrix, it's recommended to manage this instance as a singleton.
|
||||
*
|
||||
* @param context the application context
|
||||
* @param matrixConfiguration global configuration that will be used for every [org.matrix.android.sdk.api.session.Session]
|
||||
*/
|
||||
class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
|
||||
@Inject internal lateinit var legacySessionImporter: LegacySessionImporter
|
||||
@Inject internal lateinit var authenticationService: AuthenticationService
|
||||
@ -61,89 +64,72 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||
@Inject internal lateinit var lightweightSettingsStorage: LightweightSettingsStorage
|
||||
|
||||
init {
|
||||
Monarchy.init(context)
|
||||
DaggerMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
|
||||
if (context.applicationContext !is Configuration.Provider) {
|
||||
val appContext = context.applicationContext
|
||||
Monarchy.init(appContext)
|
||||
DaggerMatrixComponent.factory().create(appContext, matrixConfiguration).inject(this)
|
||||
if (appContext !is Configuration.Provider) {
|
||||
val configuration = Configuration.Builder()
|
||||
.setExecutor(Executors.newCachedThreadPool())
|
||||
.setWorkerFactory(matrixWorkerFactory)
|
||||
.build()
|
||||
WorkManager.initialize(context, configuration)
|
||||
WorkManager.initialize(appContext, configuration)
|
||||
}
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the User Agent used for any request that the SDK is making to the homeserver.
|
||||
* There is no way to change the user agent at the moment.
|
||||
*/
|
||||
fun getUserAgent() = userAgentHolder.userAgent
|
||||
|
||||
/**
|
||||
* Return the AuthenticationService.
|
||||
*/
|
||||
fun authenticationService() = authenticationService
|
||||
|
||||
/**
|
||||
* Return the RawService.
|
||||
*/
|
||||
fun rawService() = rawService
|
||||
|
||||
/**
|
||||
* Return the LightweightSettingsStorage.
|
||||
*/
|
||||
fun lightweightSettingsStorage() = lightweightSettingsStorage
|
||||
|
||||
/**
|
||||
* Return the HomeServerHistoryService.
|
||||
*/
|
||||
fun homeServerHistoryService() = homeServerHistoryService
|
||||
|
||||
/**
|
||||
* Return the legacy session importer, useful if you want to migrate an app, which was using the legacy Matrix Android Sdk.
|
||||
*/
|
||||
fun legacySessionImporter() = legacySessionImporter
|
||||
|
||||
fun workerFactory(): WorkerFactory = matrixWorkerFactory
|
||||
/**
|
||||
* Get the worker factory. The returned value has to be provided to `WorkConfiguration.Builder()`.
|
||||
*/
|
||||
fun getWorkerFactory(): WorkerFactory = matrixWorkerFactory
|
||||
|
||||
/**
|
||||
* Register an API interceptor, to be able to be notified when the specified API got a response.
|
||||
*/
|
||||
fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) {
|
||||
apiInterceptor.addListener(path, listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-register an API interceptor.
|
||||
*/
|
||||
fun unregisterApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) {
|
||||
apiInterceptor.removeListener(path, listener)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private lateinit var instance: Matrix
|
||||
private val isInit = AtomicBoolean(false)
|
||||
|
||||
/**
|
||||
* Creates a new instance of Matrix, it's recommended to manage this instance as a singleton.
|
||||
* To make use of the built in singleton use Matrix.initialize() and/or Matrix.getInstance(context) instead
|
||||
**/
|
||||
fun createInstance(context: Context, matrixConfiguration: MatrixConfiguration): Matrix {
|
||||
return Matrix(context.applicationContext, matrixConfiguration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a singleton instance of Matrix for the given MatrixConfiguration
|
||||
* This instance will be returned by Matrix.getInstance(context)
|
||||
*/
|
||||
@Deprecated("Use Matrix.createInstance and manage the instance manually")
|
||||
fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||
if (isInit.compareAndSet(false, true)) {
|
||||
instance = Matrix(context.applicationContext, matrixConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Either provides an already initialized singleton Matrix instance or queries the application context for a MatrixConfiguration.Provider
|
||||
* to lazily create and store the instance.
|
||||
*/
|
||||
@Suppress("deprecation") // suppressing warning as this method is unused but is still provided for SDK clients
|
||||
@Deprecated("Use Matrix.createInstance and manage the instance manually")
|
||||
fun getInstance(context: Context): Matrix {
|
||||
if (isInit.compareAndSet(false, true)) {
|
||||
val appContext = context.applicationContext
|
||||
if (appContext is MatrixConfiguration.Provider) {
|
||||
val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration()
|
||||
instance = Matrix(appContext, matrixConfiguration)
|
||||
} else {
|
||||
throw IllegalStateException(
|
||||
"Matrix is not initialized properly." +
|
||||
" If you want to manage your own Matrix instance use Matrix.createInstance" +
|
||||
" otherwise you should call Matrix.initialize or let your application implement MatrixConfiguration.Provider."
|
||||
)
|
||||
}
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a String with details about the Matrix SDK version
|
||||
* @return a String with details about the Matrix SDK version.
|
||||
*/
|
||||
fun getSdkVersion(): String {
|
||||
return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||
|
@ -23,7 +23,7 @@ package org.matrix.android.sdk.api
|
||||
interface MatrixCallback<in T> {
|
||||
|
||||
/**
|
||||
* On success method, default to no-op
|
||||
* On success method, default to no-op.
|
||||
* @param data the data successfully returned from the async function
|
||||
*/
|
||||
fun onSuccess(data: T) {
|
||||
@ -31,7 +31,7 @@ interface MatrixCallback<in T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* On failure method, default to no-op
|
||||
* On failure method, default to no-op.
|
||||
* @param failure the failure data returned from the async function
|
||||
*/
|
||||
fun onFailure(failure: Throwable) {
|
||||
@ -40,6 +40,6 @@ interface MatrixCallback<in T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic no op implementation
|
||||
* Basic no op implementation.
|
||||
*/
|
||||
class NoOpMatrixCallback<T> : MatrixCallback<T>
|
||||
|
@ -46,7 +46,7 @@ data class MatrixConfiguration(
|
||||
*/
|
||||
val proxy: Proxy? = null,
|
||||
/**
|
||||
* TLS versions and cipher suites limitation for unauthenticated requests
|
||||
* TLS versions and cipher suites limitation for unauthenticated requests.
|
||||
*/
|
||||
val connectionSpec: ConnectionSpec = ConnectionSpec.RESTRICTED_TLS,
|
||||
/**
|
||||
@ -62,16 +62,7 @@ data class MatrixConfiguration(
|
||||
*/
|
||||
val roomDisplayNameFallbackProvider: RoomDisplayNameFallbackProvider,
|
||||
/**
|
||||
* Thread messages default enable/disabled value
|
||||
* Thread messages default enable/disabled value.
|
||||
*/
|
||||
val threadMessagesEnabledDefault: Boolean = false,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Can be implemented by your Application class.
|
||||
*/
|
||||
@Deprecated("Use Matrix.createInstance and manage the instance manually instead of Matrix.getInstance")
|
||||
interface Provider {
|
||||
fun providesMatrixConfiguration(): MatrixConfiguration
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -17,7 +17,7 @@
|
||||
package org.matrix.android.sdk.api
|
||||
|
||||
/**
|
||||
* This object define some global constants regarding the Matrix specification
|
||||
* This object define some global constants regarding the Matrix specification.
|
||||
*/
|
||||
object MatrixConstants {
|
||||
/**
|
||||
|
@ -147,7 +147,7 @@ object MatrixPatterns {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract server name from a matrix id
|
||||
* Extract server name from a matrix id.
|
||||
*
|
||||
* @param matrixId
|
||||
* @return null if not found or if matrixId is null
|
||||
@ -172,7 +172,7 @@ object MatrixPatterns {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the domain form a userId
|
||||
* Return the domain form a userId.
|
||||
* Examples:
|
||||
* - "@alice:domain.org".getDomain() will return "domain.org"
|
||||
* - "@bob:domain.org:3455".getDomain() will return "domain.org:3455"
|
||||
|
@ -17,21 +17,21 @@
|
||||
package org.matrix.android.sdk.api
|
||||
|
||||
/**
|
||||
* This class contains pattern to match Matrix Url, aka mxc urls
|
||||
* This class contains pattern to match Matrix Url, aka mxc urls.
|
||||
*/
|
||||
object MatrixUrls {
|
||||
/**
|
||||
* "mxc" scheme, including "://". So "mxc://"
|
||||
* "mxc" scheme, including "://". So "mxc://".
|
||||
*/
|
||||
const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
|
||||
|
||||
/**
|
||||
* Return true if the String starts with "mxc://"
|
||||
* Return true if the String starts with "mxc://".
|
||||
*/
|
||||
fun String.isMxcUrl() = startsWith(MATRIX_CONTENT_URI_SCHEME)
|
||||
|
||||
/**
|
||||
* Remove the "mxc://" prefix. No op if the String is not a Mxc URL
|
||||
* Remove the "mxc://" prefix. No op if the String is not a Mxc URL.
|
||||
*/
|
||||
fun String.removeMxcPrefix() = removePrefix(MATRIX_CONTENT_URI_SCHEME)
|
||||
}
|
||||
|
@ -40,12 +40,12 @@ interface AuthenticationService {
|
||||
suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult
|
||||
|
||||
/**
|
||||
* Get a SSO url
|
||||
* Get a SSO url.
|
||||
*/
|
||||
fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String?
|
||||
|
||||
/**
|
||||
* Get the sign in or sign up fallback URL
|
||||
* Get the sign in or sign up fallback URL.
|
||||
*/
|
||||
fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String?
|
||||
|
||||
@ -64,17 +64,17 @@ interface AuthenticationService {
|
||||
fun getRegistrationWizard(): RegistrationWizard
|
||||
|
||||
/**
|
||||
* True when login and password has been sent with success to the homeserver
|
||||
* True when login and password has been sent with success to the homeserver.
|
||||
*/
|
||||
val isRegistrationStarted: Boolean
|
||||
|
||||
/**
|
||||
* Cancel pending login or pending registration
|
||||
* Cancel pending login or pending registration.
|
||||
*/
|
||||
suspend fun cancelPendingLoginOrRegistration()
|
||||
|
||||
/**
|
||||
* Reset all pending settings, including current HomeServerConnectionConfig
|
||||
* Reset all pending settings, including current HomeServerConnectionConfig.
|
||||
*/
|
||||
suspend fun reset()
|
||||
|
||||
@ -91,20 +91,20 @@ interface AuthenticationService {
|
||||
fun getLastAuthenticatedSession(): Session?
|
||||
|
||||
/**
|
||||
* Create a session after a SSO successful login
|
||||
* Create a session after a SSO successful login.
|
||||
*/
|
||||
suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials): Session
|
||||
|
||||
/**
|
||||
* Perform a wellknown request, using the domain from the matrixId
|
||||
* Perform a wellknown request, using the domain from the matrixId.
|
||||
*/
|
||||
suspend fun getWellKnownData(matrixId: String,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult
|
||||
|
||||
/**
|
||||
* Authenticate with a matrixId and a password
|
||||
* Usually call this after a successful call to getWellKnownData()
|
||||
* Authenticate with a matrixId and a password.
|
||||
* Usually call this after a successful call to getWellKnownData().
|
||||
* @param homeServerConnectionConfig the information about the homeserver and other configuration
|
||||
* @param matrixId the matrixId of the user
|
||||
* @param password the password of the account
|
||||
|
@ -20,7 +20,7 @@ import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
|
||||
/**
|
||||
* This class provides the authentication data by using user and password
|
||||
* This class provides the authentication data by using user and password.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenBasedAuth(
|
||||
|
@ -20,7 +20,7 @@ import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
|
||||
/**
|
||||
* This class provides the authentication data by using user and password
|
||||
* This class provides the authentication data by using user and password.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UserPasswordAuth(
|
||||
|
@ -37,7 +37,7 @@ data class Credentials(
|
||||
*/
|
||||
@Json(name = "access_token") val accessToken: String,
|
||||
/**
|
||||
* Not documented
|
||||
* Not documented.
|
||||
*/
|
||||
@Json(name = "refresh_token") val refreshToken: String?,
|
||||
/**
|
||||
|
@ -22,12 +22,12 @@ package org.matrix.android.sdk.api.auth.data
|
||||
*/
|
||||
data class SessionParams(
|
||||
/**
|
||||
* Please consider using shortcuts instead
|
||||
* Please consider using shortcuts instead.
|
||||
*/
|
||||
val credentials: Credentials,
|
||||
|
||||
/**
|
||||
* Please consider using shortcuts instead
|
||||
* Please consider using shortcuts instead.
|
||||
*/
|
||||
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
|
||||
@ -41,12 +41,12 @@ data class SessionParams(
|
||||
*/
|
||||
|
||||
/**
|
||||
* The userId of the session (Ex: "@user:domain.org")
|
||||
* The userId of the session (Ex: "@user:domain.org").
|
||||
*/
|
||||
val userId = credentials.userId
|
||||
|
||||
/**
|
||||
* The deviceId of the session (Ex: "ABCDEFGH")
|
||||
* The deviceId of the session (Ex: "ABCDEFGH").
|
||||
*/
|
||||
val deviceId = credentials.deviceId
|
||||
|
||||
@ -62,12 +62,12 @@ data class SessionParams(
|
||||
val homeServerUrlBase = homeServerConnectionConfig.homeServerUriBase.toString()
|
||||
|
||||
/**
|
||||
* The current homeserver host, using what has been entered by the user during login phase
|
||||
* The current homeserver host, using what has been entered by the user during login phase.
|
||||
*/
|
||||
val homeServerHost = homeServerConnectionConfig.homeServerUri.host
|
||||
|
||||
/**
|
||||
* The default identity server url if any, returned by the homeserver during login phase
|
||||
* The default identity server url if any, returned by the homeserver during login phase.
|
||||
*/
|
||||
val defaultIdentityServerUrl = homeServerConnectionConfig.identityServerUri?.toString()
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ import org.matrix.android.sdk.api.util.JsonDict
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* .
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WellKnown(
|
||||
|
@ -26,6 +26,7 @@ import com.squareup.moshi.JsonClass
|
||||
* "base_url": "https://vector.im"
|
||||
* }
|
||||
* </pre>
|
||||
* .
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WellKnownBaseConfig(
|
||||
|
@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.util.JsonDict
|
||||
*/
|
||||
interface LoginWizard {
|
||||
/**
|
||||
* Get some information about a matrixId: displayName and avatar url
|
||||
* Get some information about a matrixId: displayName and avatar url.
|
||||
*/
|
||||
suspend fun getProfileInfo(matrixId: String): LoginProfileInfo
|
||||
|
||||
|
@ -73,7 +73,7 @@ data class RegistrationFlowResponse(
|
||||
)
|
||||
|
||||
/**
|
||||
* Convert to something easier to handle on client side
|
||||
* Convert to something easier to handle on client side.
|
||||
*/
|
||||
fun RegistrationFlowResponse.toFlowResult(): FlowResult {
|
||||
// Get all the returned stages
|
||||
|
@ -32,7 +32,7 @@ const val MXCRYPTO_ALGORITHM_MEGOLM = "m.megolm.v1.aes-sha2"
|
||||
const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-sha2"
|
||||
|
||||
/**
|
||||
* Secured Shared Storage algorithm constant
|
||||
* Secured Shared Storage algorithm constant.
|
||||
*/
|
||||
const val SSSS_ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2"
|
||||
|
||||
|
@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentatio
|
||||
import org.matrix.android.sdk.internal.crypto.verification.getEmojiForCode
|
||||
|
||||
/**
|
||||
* Provide all the emojis used for SAS verification (for debug purpose)
|
||||
* Provide all the emojis used for SAS verification (for debug purpose).
|
||||
*/
|
||||
fun getAllVerificationEmojis(): List<EmojiRepresentation> {
|
||||
return (0..63).map { getEmojiForCode(it) }
|
||||
|
@ -31,5 +31,11 @@ data class MXCryptoConfig constructor(
|
||||
* If set to false, the request will be forwarded to the application layer; in this
|
||||
* case the application can decide to prompt the user.
|
||||
*/
|
||||
val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true
|
||||
val discardRoomKeyRequestsFromUntrustedDevices: Boolean = true,
|
||||
|
||||
/**
|
||||
* Currently megolm keys are requested to the sender device and to all of our devices.
|
||||
* You can limit request only to your sessions by turning this setting to `true`
|
||||
*/
|
||||
val limitRoomKeyRequestsToMyDevices: Boolean = false,
|
||||
)
|
||||
|
@ -24,6 +24,6 @@ fun CharSequence.ensurePrefix(prefix: CharSequence): CharSequence {
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a new line and then the provided string
|
||||
* Append a new line and then the provided string.
|
||||
*/
|
||||
fun StringBuilder.appendNl(str: String) = append("\n").append(str)
|
||||
|
@ -47,7 +47,7 @@ fun Throwable.shouldBeRetried() = this is Failure.NetworkConnection ||
|
||||
isLimitExceededError()
|
||||
|
||||
/**
|
||||
* Get the retry delay in case of rate limit exceeded error, adding 100 ms, of defaultValue otherwise
|
||||
* Get the retry delay in case of rate limit exceeded error, adding 100 ms, of defaultValue otherwise.
|
||||
*/
|
||||
fun Throwable.getRetryDelay(defaultValue: Long): Long {
|
||||
return (this as? Failure.ServerError)
|
||||
|
@ -17,11 +17,11 @@
|
||||
package org.matrix.android.sdk.api.failure
|
||||
|
||||
/**
|
||||
* This enum provide the reason why the SDK request an initial sync to the application
|
||||
* This enum provide the reason why the SDK request an initial sync to the application.
|
||||
*/
|
||||
enum class InitialSyncRequestReason {
|
||||
/**
|
||||
* The list of ignored users has changed, and at least one user who was ignored is not ignored anymore
|
||||
* The list of ignored users has changed, and at least one user who was ignored is not ignored anymore.
|
||||
*/
|
||||
IGNORED_USERS_LIST_CHANGE,
|
||||
}
|
||||
|
@ -28,9 +28,9 @@ import org.matrix.android.sdk.api.util.JsonDict
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MatrixError(
|
||||
/** unique string which can be used to handle an error message */
|
||||
/** unique string which can be used to handle an error message. */
|
||||
@Json(name = "errcode") val code: String,
|
||||
/** human-readable error message */
|
||||
/** human-readable error message. */
|
||||
@Json(name = "error") val message: String,
|
||||
|
||||
// For M_CONSENT_NOT_GIVEN
|
||||
@ -92,19 +92,19 @@ data class MatrixError(
|
||||
/** Sent when the room alias given to the createRoom API is already in use. */
|
||||
const val M_ROOM_IN_USE = "M_ROOM_IN_USE"
|
||||
|
||||
/** (Not documented yet) */
|
||||
/** (Not documented yet). */
|
||||
const val M_BAD_PAGINATION = "M_BAD_PAGINATION"
|
||||
|
||||
/** The request was not correctly authorized. Usually due to login failures. */
|
||||
const val M_UNAUTHORIZED = "M_UNAUTHORIZED"
|
||||
|
||||
/** (Not documented yet) */
|
||||
/** (Not documented yet). */
|
||||
const val M_OLD_VERSION = "M_OLD_VERSION"
|
||||
|
||||
/** The server did not understand the request. */
|
||||
const val M_UNRECOGNIZED = "M_UNRECOGNIZED"
|
||||
|
||||
/** (Not documented yet) */
|
||||
/** (Not documented yet). */
|
||||
const val M_LOGIN_EMAIL_URL_NOT_YET = "M_LOGIN_EMAIL_URL_NOT_YET"
|
||||
|
||||
/** Authentication could not be performed on the third party identifier. */
|
||||
@ -122,7 +122,7 @@ data class MatrixError(
|
||||
/** The request or entity was too large. */
|
||||
const val M_TOO_LARGE = "M_TOO_LARGE"
|
||||
|
||||
/** (Not documented yet) */
|
||||
/** (Not documented yet). */
|
||||
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
||||
|
||||
/** The request cannot be completed because the homeserver has reached a resource limit imposed on it. For example,
|
||||
@ -176,10 +176,10 @@ data class MatrixError(
|
||||
/** The user is unable to reject an invite to join the server notices room. See the Server Notices module for more information. */
|
||||
const val M_CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM"
|
||||
|
||||
/** (Not documented yet) */
|
||||
/** (Not documented yet). */
|
||||
const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||
|
||||
/** (Not documented yet) */
|
||||
/** (Not documented yet). */
|
||||
const val M_WEAK_PASSWORD = "M_WEAK_PASSWORD"
|
||||
|
||||
/** The provided password's length is shorter than the minimum length required by the server. */
|
||||
|
@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.federation
|
||||
|
||||
interface FederationService {
|
||||
/**
|
||||
* Get information about the homeserver
|
||||
* Get information about the homeserver.
|
||||
*/
|
||||
suspend fun getFederationVersion(): FederationVersion
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
package org.matrix.android.sdk.api.listeners
|
||||
|
||||
/**
|
||||
* Interface to send a progress info
|
||||
* Interface to send a progress info.
|
||||
*/
|
||||
interface ProgressListener {
|
||||
/**
|
||||
|
@ -17,7 +17,7 @@
|
||||
package org.matrix.android.sdk.api.listeners
|
||||
|
||||
/**
|
||||
* Interface to send a progress info
|
||||
* Interface to send a progress info.
|
||||
*/
|
||||
interface StepProgressListener {
|
||||
|
||||
|
@ -22,15 +22,15 @@ package org.matrix.android.sdk.api.logger
|
||||
* val loggerTag = LoggerTag("MyTag", LoggerTag.VOIP)
|
||||
* Timber.tag(loggerTag.value).v("My log message")
|
||||
*/
|
||||
open class LoggerTag(_value: String, parentTag: LoggerTag? = null) {
|
||||
open class LoggerTag(name: String, parentTag: LoggerTag? = null) {
|
||||
|
||||
object SYNC : LoggerTag("SYNC")
|
||||
object VOIP : LoggerTag("VOIP")
|
||||
object CRYPTO : LoggerTag("CRYPTO")
|
||||
|
||||
val value: String = if (parentTag == null) {
|
||||
_value
|
||||
name
|
||||
} else {
|
||||
"${parentTag.value}/$_value"
|
||||
"${parentTag.value}/$name"
|
||||
}
|
||||
}
|
||||
|
@ -36,19 +36,19 @@ sealed interface QueryStringValue {
|
||||
|
||||
enum class Case {
|
||||
/**
|
||||
* Match query sensitive to case
|
||||
* Match query sensitive to case.
|
||||
*/
|
||||
SENSITIVE,
|
||||
|
||||
/**
|
||||
* Match query insensitive to case, this only works for Latin-1 character sets
|
||||
* Match query insensitive to case, this only works for Latin-1 character sets.
|
||||
*/
|
||||
INSENSITIVE,
|
||||
|
||||
/**
|
||||
* Match query with input normalized (case insensitive)
|
||||
* Works around Realms inability to sort or filter by case for non Latin-1 character sets
|
||||
* Expects the target field to contain normalized data
|
||||
* Match query with input normalized (case insensitive).
|
||||
* Works around Realms inability to sort or filter by case for non Latin-1 character sets.
|
||||
* Expects the target field to contain normalized data.
|
||||
*
|
||||
* @see org.matrix.android.sdk.internal.util.Normalizer.normalize
|
||||
*/
|
||||
|
@ -23,19 +23,19 @@ import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||
*/
|
||||
interface RawService {
|
||||
/**
|
||||
* Get a URL, either from cache or from the remote server, depending on the cache strategy
|
||||
* Get a URL, either from cache or from the remote server, depending on the cache strategy.
|
||||
*/
|
||||
suspend fun getUrl(url: String, cacheStrategy: CacheStrategy): String
|
||||
|
||||
/**
|
||||
* Specific case for the well-known file. Cache validity is 8 hours
|
||||
* Specific case for the well-known file. Cache validity is 8 hours.
|
||||
* @param domain the domain to get the .well-known file, for instance "matrix.org".
|
||||
* The URL will be "https://{domain}/.well-known/matrix/client"
|
||||
*/
|
||||
suspend fun getWellknown(domain: String): String
|
||||
|
||||
/**
|
||||
* Clear all the cache data
|
||||
* Clear all the cache data.
|
||||
*/
|
||||
suspend fun clearCache()
|
||||
}
|
||||
|
@ -72,23 +72,23 @@ interface Session {
|
||||
val coroutineDispatchers: MatrixCoroutineDispatchers
|
||||
|
||||
/**
|
||||
* The params associated to the session
|
||||
* The params associated to the session.
|
||||
*/
|
||||
val sessionParams: SessionParams
|
||||
|
||||
/**
|
||||
* The session is valid, i.e. it has a valid token so far
|
||||
* The session is valid, i.e. it has a valid token so far.
|
||||
*/
|
||||
val isOpenable: Boolean
|
||||
|
||||
/**
|
||||
* Useful shortcut to get access to the userId
|
||||
* Useful shortcut to get access to the userId.
|
||||
*/
|
||||
val myUserId: String
|
||||
get() = sessionParams.userId
|
||||
|
||||
/**
|
||||
* The sessionId
|
||||
* The sessionId.
|
||||
*/
|
||||
val sessionId: String
|
||||
|
||||
@ -99,16 +99,16 @@ interface Session {
|
||||
fun open()
|
||||
|
||||
/**
|
||||
* Requires a one time background sync
|
||||
* Requires a one time background sync.
|
||||
*/
|
||||
fun requireBackgroundSync()
|
||||
|
||||
/**
|
||||
* Launches infinite self rescheduling background syncs via the WorkManager
|
||||
* Launches infinite self rescheduling background syncs via the WorkManager.
|
||||
*
|
||||
* While dozing, syncs will only occur during maintenance windows
|
||||
* While dozing, syncs will only occur during maintenance windows.
|
||||
* For reliability it's recommended to also start a long running foreground service
|
||||
* along with disabling battery optimizations
|
||||
* along with disabling battery optimizations.
|
||||
*/
|
||||
fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long)
|
||||
|
||||
@ -125,7 +125,7 @@ interface Session {
|
||||
fun stopSync()
|
||||
|
||||
/**
|
||||
* Clear cache of the session
|
||||
* Clear cache of the session.
|
||||
*/
|
||||
suspend fun clearCache()
|
||||
|
||||
@ -147,7 +147,7 @@ interface Session {
|
||||
fun syncFlow(): SharedFlow<SyncResponse>
|
||||
|
||||
/**
|
||||
* This methods return true if an initial sync has been processed
|
||||
* This methods return true if an initial sync has been processed.
|
||||
*/
|
||||
fun hasAlreadySynced(): Boolean
|
||||
|
||||
@ -162,187 +162,187 @@ interface Session {
|
||||
fun contentUrlResolver(): ContentUrlResolver
|
||||
|
||||
/**
|
||||
* Returns the ContentUploadProgressTracker associated with the session
|
||||
* Returns the ContentUploadProgressTracker associated with the session.
|
||||
*/
|
||||
fun contentUploadProgressTracker(): ContentUploadStateTracker
|
||||
|
||||
/**
|
||||
* Returns the TypingUsersTracker associated with the session
|
||||
* Returns the TypingUsersTracker associated with the session.
|
||||
*/
|
||||
fun typingUsersTracker(): TypingUsersTracker
|
||||
|
||||
/**
|
||||
* Returns the ContentDownloadStateTracker associated with the session
|
||||
* Returns the ContentDownloadStateTracker associated with the session.
|
||||
*/
|
||||
fun contentDownloadProgressTracker(): ContentDownloadStateTracker
|
||||
|
||||
/**
|
||||
* Returns the cryptoService associated with the session
|
||||
* Returns the cryptoService associated with the session.
|
||||
*/
|
||||
fun cryptoService(): CryptoService
|
||||
|
||||
/**
|
||||
* Returns the ContentScannerService associated with the session
|
||||
* Returns the ContentScannerService associated with the session.
|
||||
*/
|
||||
fun contentScannerService(): ContentScannerService
|
||||
|
||||
/**
|
||||
* Returns the identity service associated with the session
|
||||
* Returns the identity service associated with the session.
|
||||
*/
|
||||
fun identityService(): IdentityService
|
||||
|
||||
/**
|
||||
* Returns the HomeServerCapabilities service associated with the session
|
||||
* Returns the HomeServerCapabilities service associated with the session.
|
||||
*/
|
||||
fun homeServerCapabilitiesService(): HomeServerCapabilitiesService
|
||||
|
||||
/**
|
||||
* Returns the RoomService associated with the session
|
||||
* Returns the RoomService associated with the session.
|
||||
*/
|
||||
fun roomService(): RoomService
|
||||
|
||||
/**
|
||||
* Returns the RoomDirectoryService associated with the session
|
||||
* Returns the RoomDirectoryService associated with the session.
|
||||
*/
|
||||
fun roomDirectoryService(): RoomDirectoryService
|
||||
|
||||
/**
|
||||
* Returns the GroupService associated with the session
|
||||
* Returns the GroupService associated with the session.
|
||||
*/
|
||||
fun groupService(): GroupService
|
||||
|
||||
/**
|
||||
* Returns the UserService associated with the session
|
||||
* Returns the UserService associated with the session.
|
||||
*/
|
||||
fun userService(): UserService
|
||||
|
||||
/**
|
||||
* Returns the SignOutService associated with the session
|
||||
* Returns the SignOutService associated with the session.
|
||||
*/
|
||||
fun signOutService(): SignOutService
|
||||
|
||||
/**
|
||||
* Returns the FilterService associated with the session
|
||||
* Returns the FilterService associated with the session.
|
||||
*/
|
||||
fun filterService(): FilterService
|
||||
|
||||
/**
|
||||
* Returns the PushRuleService associated with the session
|
||||
* Returns the PushRuleService associated with the session.
|
||||
*/
|
||||
fun pushRuleService(): PushRuleService
|
||||
|
||||
/**
|
||||
* Returns the PushersService associated with the session
|
||||
* Returns the PushersService associated with the session.
|
||||
*/
|
||||
fun pushersService(): PushersService
|
||||
|
||||
/**
|
||||
* Returns the EventService associated with the session
|
||||
* Returns the EventService associated with the session.
|
||||
*/
|
||||
fun eventService(): EventService
|
||||
|
||||
/**
|
||||
* Returns the TermsService associated with the session
|
||||
* Returns the TermsService associated with the session.
|
||||
*/
|
||||
fun termsService(): TermsService
|
||||
|
||||
/**
|
||||
* Returns the SyncStatusService associated with the session
|
||||
* Returns the SyncStatusService associated with the session.
|
||||
*/
|
||||
fun syncStatusService(): SyncStatusService
|
||||
|
||||
/**
|
||||
* Returns the SecureStorageService associated with the session
|
||||
* Returns the SecureStorageService associated with the session.
|
||||
*/
|
||||
fun secureStorageService(): SecureStorageService
|
||||
|
||||
/**
|
||||
* Returns the ProfileService associated with the session
|
||||
* Returns the ProfileService associated with the session.
|
||||
*/
|
||||
fun profileService(): ProfileService
|
||||
|
||||
/**
|
||||
* Returns the PresenceService associated with the session
|
||||
* Returns the PresenceService associated with the session.
|
||||
*/
|
||||
fun presenceService(): PresenceService
|
||||
|
||||
/**
|
||||
* Returns the AccountService associated with the session
|
||||
* Returns the AccountService associated with the session.
|
||||
*/
|
||||
fun accountService(): AccountService
|
||||
|
||||
/**
|
||||
* Returns the ToDeviceService associated with the session
|
||||
* Returns the ToDeviceService associated with the session.
|
||||
*/
|
||||
fun toDeviceService(): ToDeviceService
|
||||
|
||||
/**
|
||||
* Returns the EventStreamService associated with the session
|
||||
* Returns the EventStreamService associated with the session.
|
||||
*/
|
||||
fun eventStreamService(): EventStreamService
|
||||
|
||||
/**
|
||||
* Returns the widget service associated with the session
|
||||
* Returns the widget service associated with the session.
|
||||
*/
|
||||
fun widgetService(): WidgetService
|
||||
|
||||
/**
|
||||
* Returns the media service associated with the session
|
||||
* Returns the media service associated with the session.
|
||||
*/
|
||||
fun mediaService(): MediaService
|
||||
|
||||
/**
|
||||
* Returns the integration manager service associated with the session
|
||||
* Returns the integration manager service associated with the session.
|
||||
*/
|
||||
fun integrationManagerService(): IntegrationManagerService
|
||||
|
||||
/**
|
||||
* Returns the call signaling service associated with the session
|
||||
* Returns the call signaling service associated with the session.
|
||||
*/
|
||||
fun callSignalingService(): CallSignalingService
|
||||
|
||||
/**
|
||||
* Returns the file download service associated with the session
|
||||
* Returns the file download service associated with the session.
|
||||
*/
|
||||
fun fileService(): FileService
|
||||
|
||||
/**
|
||||
* Returns the permalink service associated with the session
|
||||
* Returns the permalink service associated with the session.
|
||||
*/
|
||||
fun permalinkService(): PermalinkService
|
||||
|
||||
/**
|
||||
* Returns the search service associated with the session
|
||||
* Returns the search service associated with the session.
|
||||
*/
|
||||
fun searchService(): SearchService
|
||||
|
||||
/**
|
||||
* Returns the federation service associated with the session
|
||||
* Returns the federation service associated with the session.
|
||||
*/
|
||||
fun federationService(): FederationService
|
||||
|
||||
/**
|
||||
* Returns the third party service associated with the session
|
||||
* Returns the third party service associated with the session.
|
||||
*/
|
||||
fun thirdPartyService(): ThirdPartyService
|
||||
|
||||
/**
|
||||
* Returns the space service associated with the session
|
||||
* Returns the space service associated with the session.
|
||||
*/
|
||||
fun spaceService(): SpaceService
|
||||
|
||||
/**
|
||||
* Returns the open id service associated with the session
|
||||
* Returns the open id service associated with the session.
|
||||
*/
|
||||
fun openIdService(): OpenIdService
|
||||
|
||||
/**
|
||||
* Returns the account data service associated with the session
|
||||
* Returns the account data service associated with the session.
|
||||
*/
|
||||
fun accountDataService(): SessionAccountDataService
|
||||
|
||||
/**
|
||||
* Returns the SharedSecretStorageService associated with the session
|
||||
* Returns the SharedSecretStorageService associated with the session.
|
||||
*/
|
||||
fun sharedSecretStorageService(): SharedSecretStorageService
|
||||
|
||||
@ -377,8 +377,8 @@ interface Session {
|
||||
/**
|
||||
* Possible cases:
|
||||
* - The access token is not valid anymore,
|
||||
* - a M_CONSENT_NOT_GIVEN error has been received from the homeserver
|
||||
* See [GlobalError] for all the possible cases
|
||||
* - a M_CONSENT_NOT_GIVEN error has been received from the homeserver;
|
||||
* See [GlobalError] for all the possible cases.
|
||||
*/
|
||||
fun onGlobalError(session: Session, globalError: GlobalError) = Unit
|
||||
}
|
||||
@ -386,7 +386,7 @@ interface Session {
|
||||
fun getUiaSsoFallbackUrl(authenticationSessionId: String): String
|
||||
|
||||
/**
|
||||
* Maintenance API, allows to print outs info on DB size to logcat
|
||||
* Maintenance API, allows to print outs info on DB size to logcat.
|
||||
*/
|
||||
fun logDbUsageInfo()
|
||||
}
|
||||
|
@ -21,16 +21,16 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
|
||||
/**
|
||||
* Get a room using the RoomService of a Session
|
||||
* Get a room using the RoomService of a Session.
|
||||
*/
|
||||
fun Session.getRoom(roomId: String): Room? = roomService().getRoom(roomId)
|
||||
|
||||
/**
|
||||
* Get a room summary using the RoomService of a Session
|
||||
* Get a room summary using the RoomService of a Session.
|
||||
*/
|
||||
fun Session.getRoomSummary(roomIdOrAlias: String): RoomSummary? = roomService().getRoomSummary(roomIdOrAlias)
|
||||
|
||||
/**
|
||||
* Get a user using the UserService of a Session
|
||||
* Get a user using the UserService of a Session.
|
||||
*/
|
||||
fun Session.getUser(userId: String): User? = userService().getUser(userId)
|
||||
|
@ -21,9 +21,8 @@ import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import java.util.UUID
|
||||
|
||||
interface ToDeviceService {
|
||||
|
||||
/**
|
||||
* Send an event to a specific list of devices
|
||||
* Send an event to a specific list of devices.
|
||||
*/
|
||||
suspend fun sendToDevice(eventType: String, contentMap: MXUsersDevicesMap<Any>, txnId: String? = UUID.randomUUID().toString())
|
||||
|
||||
|
@ -26,12 +26,12 @@ import org.matrix.android.sdk.api.util.Optional
|
||||
*/
|
||||
interface SessionAccountDataService {
|
||||
/**
|
||||
* Retrieve the account data with the provided type or null if not found
|
||||
* Retrieve the account data with the provided type or null if not found.
|
||||
*/
|
||||
fun getUserAccountDataEvent(type: String): UserAccountDataEvent?
|
||||
|
||||
/**
|
||||
* Observe the account data with the provided type
|
||||
* Observe the account data with the provided type.
|
||||
*/
|
||||
fun getLiveUserAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>>
|
||||
|
||||
@ -60,7 +60,7 @@ interface SessionAccountDataService {
|
||||
fun getLiveRoomAccountDataEvents(types: Set<String>): LiveData<List<RoomAccountDataEvent>>
|
||||
|
||||
/**
|
||||
* Update the account data with the provided type and the provided account data content
|
||||
* Update the account data with the provided type and the provided account data content.
|
||||
*/
|
||||
suspend fun updateUserAccountData(type: String, content: Content)
|
||||
}
|
||||
|
@ -39,32 +39,32 @@ interface CallListener {
|
||||
fun onCallAnswerReceived(callAnswerContent: CallAnswerContent)
|
||||
|
||||
/**
|
||||
* Called when a called has been hung up
|
||||
* Called when a called has been hung up.
|
||||
*/
|
||||
fun onCallHangupReceived(callHangupContent: CallHangupContent)
|
||||
|
||||
/**
|
||||
* Called when a called has been rejected
|
||||
* Called when a called has been rejected.
|
||||
*/
|
||||
fun onCallRejectReceived(callRejectContent: CallRejectContent)
|
||||
|
||||
/**
|
||||
* Called when an answer has been selected
|
||||
* Called when an answer has been selected.
|
||||
*/
|
||||
fun onCallSelectAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent)
|
||||
|
||||
/**
|
||||
* Called when a negotiation is sent
|
||||
* Called when a negotiation is sent.
|
||||
*/
|
||||
fun onCallNegotiateReceived(callNegotiateContent: CallNegotiateContent)
|
||||
|
||||
/**
|
||||
* Called when the call has been managed by an other session
|
||||
* Called when the call has been managed by an other session.
|
||||
*/
|
||||
fun onCallManagedByOtherSession(callId: String)
|
||||
|
||||
/**
|
||||
* Called when an asserted identity event is received
|
||||
* Called when an asserted identity event is received.
|
||||
*/
|
||||
fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ interface CallSignalingService {
|
||||
suspend fun getTurnServer(): TurnServerResponse
|
||||
|
||||
/**
|
||||
* Create an outgoing call
|
||||
* Create an outgoing call.
|
||||
*/
|
||||
fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall
|
||||
|
||||
|
@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
|
||||
|
||||
sealed class CallState {
|
||||
|
||||
/** Idle, setting up objects */
|
||||
/** Idle, setting up objects. */
|
||||
object Idle : CallState()
|
||||
|
||||
/**
|
||||
|
@ -35,7 +35,7 @@ interface MxCallDetail {
|
||||
}
|
||||
|
||||
/**
|
||||
* Define both an incoming call and on outgoing call
|
||||
* Define both an incoming call and on outgoing call.
|
||||
*/
|
||||
interface MxCall : MxCallDetail {
|
||||
|
||||
@ -46,13 +46,13 @@ interface MxCall : MxCallDetail {
|
||||
var state: CallState
|
||||
|
||||
/**
|
||||
* Pick Up the incoming call
|
||||
* It has no effect on outgoing call
|
||||
* Pick Up the incoming call.
|
||||
* It has no effect on outgoing call.
|
||||
*/
|
||||
fun accept(sdpString: String)
|
||||
|
||||
/**
|
||||
* SDP negotiation for media pause, hold/resume, ICE restarts and voice/video call up/downgrading
|
||||
* SDP negotiation for media pause, hold/resume, ICE restarts and voice/video call up/downgrading.
|
||||
*/
|
||||
fun negotiate(sdpString: String, type: SdpType)
|
||||
|
||||
@ -62,17 +62,17 @@ interface MxCall : MxCallDetail {
|
||||
fun selectAnswer()
|
||||
|
||||
/**
|
||||
* Reject an incoming call
|
||||
* Reject an incoming call.
|
||||
*/
|
||||
fun reject()
|
||||
|
||||
/**
|
||||
* End the call
|
||||
* End the call.
|
||||
*/
|
||||
fun hangUp(reason: EndCallReason? = null)
|
||||
|
||||
/**
|
||||
* Start a call
|
||||
* Start a call.
|
||||
* Send offer SDP to the other participant.
|
||||
*/
|
||||
fun offerSdp(sdpString: String)
|
||||
|
@ -29,7 +29,7 @@ interface ContentUrlResolver {
|
||||
}
|
||||
|
||||
/**
|
||||
* URL to use to upload content
|
||||
* URL to use to upload content.
|
||||
*/
|
||||
val uploadUrl: String
|
||||
|
||||
@ -42,7 +42,7 @@ interface ContentUrlResolver {
|
||||
fun resolveFullSize(contentUrl: String?): String?
|
||||
|
||||
/**
|
||||
* Get the ResolvedMethod to download a URL
|
||||
* Get the ResolvedMethod to download a URL.
|
||||
*
|
||||
* @param contentUrl the Matrix media content URI (in the form of "mxc://...").
|
||||
* @param elementToDecrypt Encryption data may be required if you use a content scanner
|
||||
|
@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningServic
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
||||
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
|
||||
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
|
||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse
|
||||
@ -35,8 +36,6 @@ import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult
|
||||
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
|
||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest
|
||||
import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
@ -76,6 +75,15 @@ interface CryptoService {
|
||||
|
||||
fun setGlobalBlacklistUnverifiedDevices(block: Boolean)
|
||||
|
||||
/**
|
||||
* Enable or disable key gossiping.
|
||||
* Default is true.
|
||||
* If set to false this device won't send key_request nor will accept key forwarded
|
||||
*/
|
||||
fun enableKeyGossiping(enable: Boolean)
|
||||
|
||||
fun isKeyGossipingEnabled(): Boolean
|
||||
|
||||
fun setRoomUnBlacklistUnverifiedDevices(roomId: String)
|
||||
|
||||
fun getDeviceTrackingStatus(userId: String): Int
|
||||
@ -94,8 +102,6 @@ interface CryptoService {
|
||||
|
||||
fun reRequestRoomKeyForEvent(event: Event)
|
||||
|
||||
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody)
|
||||
|
||||
fun addRoomKeysRequestListener(listener: GossipingRequestListener)
|
||||
|
||||
fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
|
||||
@ -142,14 +148,20 @@ interface CryptoService {
|
||||
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
||||
fun removeSessionListener(listener: NewSessionListener)
|
||||
|
||||
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
||||
fun getOutgoingRoomKeyRequestsPaged(): LiveData<PagedList<OutgoingRoomKeyRequest>>
|
||||
fun getOutgoingRoomKeyRequests(): List<OutgoingKeyRequest>
|
||||
fun getOutgoingRoomKeyRequestsPaged(): LiveData<PagedList<OutgoingKeyRequest>>
|
||||
|
||||
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
||||
fun getIncomingRoomKeyRequestsPaged(): LiveData<PagedList<IncomingRoomKeyRequest>>
|
||||
|
||||
fun getGossipingEventsTrail(): LiveData<PagedList<Event>>
|
||||
fun getGossipingEvents(): List<Event>
|
||||
/**
|
||||
* Can be called by the app layer to accept a request manually.
|
||||
* Use carefully as it is prone to social attacks.
|
||||
*/
|
||||
suspend fun manuallyAcceptRoomKeyRequest(request: IncomingRoomKeyRequest)
|
||||
|
||||
fun getGossipingEventsTrail(): LiveData<PagedList<AuditTrail>>
|
||||
fun getGossipingEvents(): List<AuditTrail>
|
||||
|
||||
// For testing shared session
|
||||
fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int>
|
||||
|
@ -28,7 +28,7 @@ sealed class MXCryptoError : Throwable() {
|
||||
data class Base(val errorType: ErrorType,
|
||||
val technicalMessage: String,
|
||||
/**
|
||||
* Describe the error with more details
|
||||
* Describe the error with more details.
|
||||
*/
|
||||
val detailedErrorDescription: String? = null) : MXCryptoError()
|
||||
|
||||
@ -63,7 +63,7 @@ sealed class MXCryptoError : Throwable() {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Resource for technicalMessage
|
||||
* Resource for technicalMessage.
|
||||
*/
|
||||
const val UNABLE_TO_ENCRYPT_REASON = "Unable to encrypt %s"
|
||||
const val UNABLE_TO_DECRYPT_REASON = "Unable to decrypt %1\$s. Algorithm: %2\$s"
|
||||
|
@ -17,7 +17,7 @@
|
||||
package org.matrix.android.sdk.api.session.crypto
|
||||
|
||||
/**
|
||||
* This listener notifies on new Megolm sessions being created
|
||||
* This listener notifies on new Megolm sessions being created.
|
||||
*/
|
||||
interface NewSessionListener {
|
||||
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.session.crypto
|
||||
|
||||
import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody
|
||||
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
|
||||
|
||||
data class RequestReply(
|
||||
val userId: String,
|
||||
val fromDevice: String?,
|
||||
val result: RequestResult
|
||||
)
|
||||
|
||||
sealed class RequestResult {
|
||||
data class Success(val chainIndex: Int) : RequestResult()
|
||||
data class Failure(val code: WithHeldCode) : RequestResult()
|
||||
}
|
||||
|
||||
data class OutgoingKeyRequest(
|
||||
var requestBody: RoomKeyRequestBody?,
|
||||
// recipients for the request map of users to list of deviceId
|
||||
val recipients: Map<String, List<String>>,
|
||||
val fromIndex: Int,
|
||||
// Unique id for this request. Used for both
|
||||
// an id within the request for later pairing with a cancellation, and for
|
||||
// the transaction id when sending the to_device messages to our local
|
||||
val requestId: String, // current state of this request
|
||||
val state: OutgoingRoomKeyRequestState,
|
||||
val results: List<RequestReply>
|
||||
) {
|
||||
/**
|
||||
* Used only for log.
|
||||
*
|
||||
* @return the room id.
|
||||
*/
|
||||
val roomId = requestBody?.roomId
|
||||
|
||||
/**
|
||||
* Used only for log.
|
||||
*
|
||||
* @return the session id
|
||||
*/
|
||||
val sessionId = requestBody?.sessionId
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user