mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-12-22 17:10:43 +03:00
[issue-2610] Merge branch 'develop' of https://github.com/vector-im/element-android into feature/issue-2610-override-nick-color-via-user-account-data
This commit is contained in:
commit
0109cdefa6
158 changed files with 3156 additions and 1238 deletions
32
CHANGES.md
32
CHANGES.md
|
@ -1,4 +1,4 @@
|
||||||
Changes in Element 1.0.15 (2020-XX-XX)
|
Changes in Element 1.0.16 (2020-XX-XX)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features ✨:
|
Features ✨:
|
||||||
|
@ -23,6 +23,36 @@ Build 🧱:
|
||||||
Test:
|
Test:
|
||||||
-
|
-
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
-
|
||||||
|
|
||||||
|
Changes in Element 1.0.15 (2020-02-03)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- Social Login support
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- SSO support for cross signing (#1062)
|
||||||
|
- Deactivate account when logged in with SSO (#1264)
|
||||||
|
- SSO UIA doesn't work (#2754)
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Fix clear cache issue: sometimes, after a clear cache, there is still a token, so the init sync service is not started.
|
||||||
|
- Sidebar too large in horizontal orientation or tablets (#475)
|
||||||
|
- UrlPreview should be updated when the url is edited and changed (#2678)
|
||||||
|
- When receiving a new pepper from identity server, use it on the next hash lookup (#2708)
|
||||||
|
- Crashes reported by PlayStore (new in 1.0.14) (#2707)
|
||||||
|
- Widgets: Support $matrix_widget_id parameter (#2748)
|
||||||
|
- Data for Worker overload (#2721)
|
||||||
|
- Fix multiple tasks
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
- Increase targetSdkVersion to 30 (#2600)
|
||||||
|
|
||||||
|
Build 🧱:
|
||||||
|
- Compile with Android SDK 30 (Android 11)
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
- Update Dagger to 2.31 version so we can use the embedded AssistedInject feature
|
- Update Dagger to 2.31 version so we can use the embedded AssistedInject feature
|
||||||
|
|
||||||
|
|
|
@ -32,11 +32,11 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,15 +18,19 @@
|
||||||
package im.vector.lib.attachmentviewer
|
package im.vector.lib.attachmentviewer
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.ScaleGestureDetector
|
import android.view.ScaleGestureDetector
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.WindowInsets
|
||||||
|
import android.view.WindowInsetsController
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.GestureDetectorCompat
|
import androidx.core.view.GestureDetectorCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
@ -94,14 +98,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
// This is important for the dispatchTouchEvent, if not we must correct
|
setDecorViewFullScreen()
|
||||||
// the touch coordinates
|
|
||||||
window.decorView.systemUiVisibility = (
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
||||||
or View.SYSTEM_UI_FLAG_IMMERSIVE)
|
|
||||||
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
|
||||||
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
|
|
||||||
|
|
||||||
views = ActivityAttachmentViewerBinding.inflate(layoutInflater)
|
views = ActivityAttachmentViewerBinding.inflate(layoutInflater)
|
||||||
setContentView(views.root)
|
setContentView(views.root)
|
||||||
|
@ -134,6 +131,29 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
private fun setDecorViewFullScreen() {
|
||||||
|
// This is important for the dispatchTouchEvent, if not we must correct
|
||||||
|
// the touch coordinates
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
// New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
window.setDecorFitsSystemWindows(false)
|
||||||
|
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE
|
||||||
|
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
||||||
|
// New API instead of FLAG_TRANSLUCENT_STATUS
|
||||||
|
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
|
||||||
|
// new API instead of FLAG_TRANSLUCENT_NAVIGATION
|
||||||
|
window.navigationBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
|
||||||
|
} else {
|
||||||
|
window.decorView.systemUiVisibility = (
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
or View.SYSTEM_UI_FLAG_IMMERSIVE)
|
||||||
|
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||||
|
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onSelectedPositionChanged(position: Int) {
|
fun onSelectedPositionChanged(position: Int) {
|
||||||
attachmentsAdapter.recyclerView?.findViewHolderForAdapterPosition(currentPosition)?.let {
|
attachmentsAdapter.recyclerView?.findViewHolderForAdapterPosition(currentPosition)?.let {
|
||||||
(it as? BaseViewHolder)?.onSelected(false)
|
(it as? BaseViewHolder)?.onSelected(false)
|
||||||
|
@ -313,11 +333,24 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
?.handleCommand(commands)
|
?.handleCommand(commands)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
private fun hideSystemUI() {
|
private fun hideSystemUI() {
|
||||||
systemUiVisibility = false
|
systemUiVisibility = false
|
||||||
// Enables regular immersive mode.
|
// Enables regular immersive mode.
|
||||||
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
|
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
|
||||||
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
// New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
window.setDecorFitsSystemWindows(false)
|
||||||
|
// new API instead of SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
|
window.decorView.windowInsetsController?.hide(WindowInsets.Type.navigationBars())
|
||||||
|
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE
|
||||||
|
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
||||||
|
// New API instead of FLAG_TRANSLUCENT_STATUS
|
||||||
|
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
|
||||||
|
// New API instead of FLAG_TRANSLUCENT_NAVIGATION
|
||||||
|
window.navigationBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
|
||||||
|
} else {
|
||||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||||
// Set the content to appear under the system bars so that the
|
// Set the content to appear under the system bars so that the
|
||||||
// content doesn't resize when the system bars hide and show.
|
// content doesn't resize when the system bars hide and show.
|
||||||
|
@ -328,13 +361,20 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Shows the system bars by removing all the flags
|
// Shows the system bars by removing all the flags
|
||||||
// except for the ones that make the content appear under the system bars.
|
// except for the ones that make the content appear under the system bars.
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
private fun showSystemUI() {
|
private fun showSystemUI() {
|
||||||
systemUiVisibility = true
|
systemUiVisibility = true
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
// New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
window.setDecorFitsSystemWindows(false)
|
||||||
|
} else {
|
||||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
6
attachment-viewer/src/main/res/values/colors.xml
Normal file
6
attachment-viewer/src/main/res/values/colors.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<color name="half_transparent_status_bar">#80000000</color>
|
||||||
|
|
||||||
|
</resources>
|
2
fastlane/metadata/android/en-US/changelogs/40100150.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40100150.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: Social Login support.
|
||||||
|
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.0.15
|
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,7 +1,6 @@
|
||||||
#Fri Jan 15 11:30:47 CET 2021
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
#distributionSha256Sum=a7ca23b3ccf265680f2bfd35f1f00b1424f4466292c7337c85d46c9641b3f053
|
distributionSha256Sum=3db89524a3981819ff28c3f979236c1274a726e146ced0c8a2020417f9bc0782
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip
|
|
||||||
|
|
|
@ -3,11 +3,11 @@ apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,12 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
testOptions.unitTests.includeAndroidResources = true
|
testOptions.unitTests.includeAndroidResources = true
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "0.0.1"
|
versionName "0.0.1"
|
||||||
// Multidex is useful for tests
|
// Multidex is useful for tests
|
||||||
|
|
|
@ -16,8 +16,18 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.account
|
package org.matrix.android.sdk.account
|
||||||
|
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
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.data.LoginFlowResult
|
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||||
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.failure.MatrixError
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
|
@ -25,12 +35,8 @@ import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
import org.matrix.android.sdk.common.SessionTestParams
|
import org.matrix.android.sdk.common.SessionTestParams
|
||||||
import org.matrix.android.sdk.common.TestConstants
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
import org.matrix.android.sdk.common.TestMatrixCallback
|
import org.matrix.android.sdk.common.TestMatrixCallback
|
||||||
import org.junit.Assert.assertTrue
|
import kotlin.coroutines.Continuation
|
||||||
import org.junit.FixMethodOrder
|
import kotlin.coroutines.resume
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.junit.runners.JUnit4
|
|
||||||
import org.junit.runners.MethodSorters
|
|
||||||
|
|
||||||
@RunWith(JUnit4::class)
|
@RunWith(JUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
@ -44,7 +50,18 @@ class DeactivateAccountTest : InstrumentedTest {
|
||||||
|
|
||||||
// Deactivate the account
|
// Deactivate the account
|
||||||
commonTestHelper.runBlockingTest {
|
commonTestHelper.runBlockingTest {
|
||||||
session.deactivateAccount(TestConstants.PASSWORD, false)
|
session.deactivateAccount(
|
||||||
|
object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(
|
||||||
|
UserPasswordAuth(
|
||||||
|
user = session.myUserId,
|
||||||
|
password = TestConstants.PASSWORD,
|
||||||
|
session = flowResponse.session
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to login on the previous account, it will fail (M_USER_DEACTIVATED)
|
// Try to login on the previous account, it will fail (M_USER_DEACTIVATED)
|
||||||
|
|
|
@ -378,7 +378,9 @@ class CommonTestHelper(context: Context) {
|
||||||
fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) }
|
fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) }
|
||||||
|
|
||||||
fun signOutAndClose(session: Session) {
|
fun signOutAndClose(session: Session) {
|
||||||
doSync<Unit>(60_000) { session.signOut(true, it) }
|
runBlockingTest(timeout = 60_000) {
|
||||||
|
session.signOut(true)
|
||||||
|
}
|
||||||
// no need signout will close
|
// no need signout will close
|
||||||
// session.close()
|
// session.close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,18 @@ package org.matrix.android.sdk.common
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
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.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
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.OutgoingSasVerificationTransaction
|
||||||
|
@ -36,17 +48,10 @@ import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Assert.assertNotNull
|
|
||||||
import org.junit.Assert.assertNull
|
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
|
|
||||||
|
@ -304,10 +309,18 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
fun initializeCrossSigning(session: Session) {
|
fun initializeCrossSigning(session: Session) {
|
||||||
mTestHelper.doSync<Unit> {
|
mTestHelper.doSync<Unit> {
|
||||||
session.cryptoService().crossSigningService()
|
session.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(UserPasswordAuth(
|
.initializeCrossSigning(
|
||||||
|
object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(
|
||||||
|
UserPasswordAuth(
|
||||||
user = session.myUserId,
|
user = session.myUserId,
|
||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD,
|
||||||
), it)
|
session = flowResponse.session
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,18 @@
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
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.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.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
@ -30,19 +41,13 @@ import org.matrix.android.sdk.common.CryptoTestHelper
|
||||||
import org.matrix.android.sdk.common.TestConstants
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
|
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
|
||||||
import org.amshove.kluent.shouldBe
|
|
||||||
import org.junit.Assert
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.FixMethodOrder
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.junit.runners.MethodSorters
|
|
||||||
import org.matrix.olm.OlmSession
|
import org.matrix.olm.OlmSession
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ref:
|
* Ref:
|
||||||
|
@ -202,10 +207,18 @@ class UnwedgingTest : InstrumentedTest {
|
||||||
// It's a trick to force key request on fail to decrypt
|
// It's a trick to force key request on fail to decrypt
|
||||||
mTestHelper.doSync<Unit> {
|
mTestHelper.doSync<Unit> {
|
||||||
bobSession.cryptoService().crossSigningService()
|
bobSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(UserPasswordAuth(
|
.initializeCrossSigning(
|
||||||
|
object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(
|
||||||
|
UserPasswordAuth(
|
||||||
user = bobSession.myUserId,
|
user = bobSession.myUserId,
|
||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD,
|
||||||
), it)
|
session = flowResponse.session
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until we received back the key
|
// Wait until we received back the key
|
||||||
|
|
|
@ -17,14 +17,6 @@
|
||||||
package org.matrix.android.sdk.internal.crypto.crosssigning
|
package org.matrix.android.sdk.internal.crypto.crosssigning
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
|
||||||
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 org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
|
@ -35,6 +27,19 @@ import org.junit.FixMethodOrder
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.MethodSorters
|
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.common.CommonTestHelper
|
||||||
|
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.internal.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
@ -49,10 +54,17 @@ class XSigningTest : InstrumentedTest {
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> {
|
mTestHelper.doSync<Unit> {
|
||||||
aliceSession.cryptoService().crossSigningService()
|
aliceSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(UserPasswordAuth(
|
.initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(
|
||||||
|
UserPasswordAuth(
|
||||||
user = aliceSession.myUserId,
|
user = aliceSession.myUserId,
|
||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD,
|
||||||
), it)
|
session = flowResponse.session
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||||
|
@ -86,8 +98,18 @@ class XSigningTest : InstrumentedTest {
|
||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD
|
||||||
)
|
)
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, it) }
|
mTestHelper.doSync<Unit> {
|
||||||
mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, it) }
|
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(aliceAuthParams)
|
||||||
|
}
|
||||||
|
}, it)
|
||||||
|
}
|
||||||
|
mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(bobAuthParams)
|
||||||
|
}
|
||||||
|
}, it) }
|
||||||
|
|
||||||
// Check that alice can see bob keys
|
// Check that alice can see bob keys
|
||||||
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
|
mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
|
||||||
|
@ -122,8 +144,16 @@ class XSigningTest : InstrumentedTest {
|
||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD
|
||||||
)
|
)
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, it) }
|
mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, it) }
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(aliceAuthParams)
|
||||||
|
}
|
||||||
|
}, it) }
|
||||||
|
mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(bobAuthParams)
|
||||||
|
}
|
||||||
|
}, it) }
|
||||||
|
|
||||||
// Check that alice can see bob keys
|
// Check that alice can see bob keys
|
||||||
val bobUserId = bobSession.myUserId
|
val bobUserId = bobSession.myUserId
|
||||||
|
|
|
@ -18,7 +18,21 @@ package org.matrix.android.sdk.internal.crypto.gossiping
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import junit.framework.TestCase.assertNotNull
|
||||||
|
import junit.framework.TestCase.assertTrue
|
||||||
|
import junit.framework.TestCase.fail
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
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.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.verification.IncomingSasVerificationTransaction
|
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.SasVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
|
@ -28,6 +42,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
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.create.CreateRoomParams
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||||
import org.matrix.android.sdk.common.SessionTestParams
|
import org.matrix.android.sdk.common.SessionTestParams
|
||||||
|
@ -40,19 +55,9 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
|
||||||
import junit.framework.TestCase.assertEquals
|
|
||||||
import junit.framework.TestCase.assertNotNull
|
|
||||||
import junit.framework.TestCase.assertTrue
|
|
||||||
import junit.framework.TestCase.fail
|
|
||||||
import org.junit.Assert
|
|
||||||
import org.junit.FixMethodOrder
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.junit.runners.MethodSorters
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
@ -200,10 +205,17 @@ class KeyShareTests : InstrumentedTest {
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> {
|
mTestHelper.doSync<Unit> {
|
||||||
aliceSession1.cryptoService().crossSigningService()
|
aliceSession1.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(UserPasswordAuth(
|
.initializeCrossSigning(
|
||||||
|
object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(
|
||||||
|
UserPasswordAuth(
|
||||||
user = aliceSession1.myUserId,
|
user = aliceSession1.myUserId,
|
||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD
|
||||||
), it)
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also bootstrap keybackup on first session
|
// Also bootstrap keybackup on first session
|
||||||
|
@ -305,10 +317,18 @@ class KeyShareTests : InstrumentedTest {
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> {
|
mTestHelper.doSync<Unit> {
|
||||||
aliceSession.cryptoService().crossSigningService()
|
aliceSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(UserPasswordAuth(
|
.initializeCrossSigning(
|
||||||
|
object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(
|
||||||
|
UserPasswordAuth(
|
||||||
user = aliceSession.myUserId,
|
user = aliceSession.myUserId,
|
||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD,
|
||||||
), it)
|
session = flowResponse.session
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an encrypted room and send a couple of messages
|
// Create an encrypted room and send a couple of messages
|
||||||
|
@ -332,10 +352,18 @@ class KeyShareTests : InstrumentedTest {
|
||||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
|
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
|
||||||
mTestHelper.doSync<Unit> {
|
mTestHelper.doSync<Unit> {
|
||||||
bobSession.cryptoService().crossSigningService()
|
bobSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(UserPasswordAuth(
|
.initializeCrossSigning(
|
||||||
|
object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(
|
||||||
|
UserPasswordAuth(
|
||||||
user = bobSession.myUserId,
|
user = bobSession.myUserId,
|
||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD,
|
||||||
), it)
|
session = flowResponse.session
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let alice invite bob
|
// Let alice invite bob
|
||||||
|
|
|
@ -17,20 +17,25 @@
|
||||||
package org.matrix.android.sdk.internal.crypto.verification.qrcode
|
package org.matrix.android.sdk.internal.crypto.verification.qrcode
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
|
||||||
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.common.CommonTestHelper
|
|
||||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
|
||||||
import org.matrix.android.sdk.common.TestConstants
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.MethodSorters
|
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.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.common.CommonTestHelper
|
||||||
|
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||||
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
@ -157,18 +162,34 @@ class VerificationTest : InstrumentedTest {
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> { callback ->
|
mTestHelper.doSync<Unit> { callback ->
|
||||||
aliceSession.cryptoService().crossSigningService()
|
aliceSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(UserPasswordAuth(
|
.initializeCrossSigning(
|
||||||
|
object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(
|
||||||
|
UserPasswordAuth(
|
||||||
user = aliceSession.myUserId,
|
user = aliceSession.myUserId,
|
||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD,
|
||||||
), callback)
|
session = flowResponse.session
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> { callback ->
|
mTestHelper.doSync<Unit> { callback ->
|
||||||
bobSession.cryptoService().crossSigningService()
|
bobSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(UserPasswordAuth(
|
.initializeCrossSigning(
|
||||||
|
object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(
|
||||||
|
UserPasswordAuth(
|
||||||
user = bobSession.myUserId,
|
user = bobSession.myUserId,
|
||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD,
|
||||||
), callback)
|
session = flowResponse.session
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
|
|
|
@ -26,6 +26,8 @@ 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.toContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
|
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
internal class UrlsExtractorTest : InstrumentedTest {
|
internal class UrlsExtractorTest : InstrumentedTest {
|
||||||
|
@ -36,6 +38,7 @@ internal class UrlsExtractorTest : InstrumentedTest {
|
||||||
fun wrongEventTypeTest() {
|
fun wrongEventTypeTest() {
|
||||||
createEvent(body = "https://matrix.org")
|
createEvent(body = "https://matrix.org")
|
||||||
.copy(type = EventType.STATE_ROOM_GUEST_ACCESS)
|
.copy(type = EventType.STATE_ROOM_GUEST_ACCESS)
|
||||||
|
.toFakeTimelineEvent()
|
||||||
.let { urlsExtractor.extract(it) }
|
.let { urlsExtractor.extract(it) }
|
||||||
.size shouldBeEqualTo 0
|
.size shouldBeEqualTo 0
|
||||||
}
|
}
|
||||||
|
@ -43,6 +46,7 @@ internal class UrlsExtractorTest : InstrumentedTest {
|
||||||
@Test
|
@Test
|
||||||
fun oneUrlTest() {
|
fun oneUrlTest() {
|
||||||
createEvent(body = "https://matrix.org")
|
createEvent(body = "https://matrix.org")
|
||||||
|
.toFakeTimelineEvent()
|
||||||
.let { urlsExtractor.extract(it) }
|
.let { urlsExtractor.extract(it) }
|
||||||
.let { result ->
|
.let { result ->
|
||||||
result.size shouldBeEqualTo 1
|
result.size shouldBeEqualTo 1
|
||||||
|
@ -53,6 +57,7 @@ internal class UrlsExtractorTest : InstrumentedTest {
|
||||||
@Test
|
@Test
|
||||||
fun withoutProtocolTest() {
|
fun withoutProtocolTest() {
|
||||||
createEvent(body = "www.matrix.org")
|
createEvent(body = "www.matrix.org")
|
||||||
|
.toFakeTimelineEvent()
|
||||||
.let { urlsExtractor.extract(it) }
|
.let { urlsExtractor.extract(it) }
|
||||||
.size shouldBeEqualTo 0
|
.size shouldBeEqualTo 0
|
||||||
}
|
}
|
||||||
|
@ -60,6 +65,7 @@ internal class UrlsExtractorTest : InstrumentedTest {
|
||||||
@Test
|
@Test
|
||||||
fun oneUrlWithParamTest() {
|
fun oneUrlWithParamTest() {
|
||||||
createEvent(body = "https://matrix.org?foo=bar")
|
createEvent(body = "https://matrix.org?foo=bar")
|
||||||
|
.toFakeTimelineEvent()
|
||||||
.let { urlsExtractor.extract(it) }
|
.let { urlsExtractor.extract(it) }
|
||||||
.let { result ->
|
.let { result ->
|
||||||
result.size shouldBeEqualTo 1
|
result.size shouldBeEqualTo 1
|
||||||
|
@ -70,6 +76,7 @@ internal class UrlsExtractorTest : InstrumentedTest {
|
||||||
@Test
|
@Test
|
||||||
fun oneUrlWithParamsTest() {
|
fun oneUrlWithParamsTest() {
|
||||||
createEvent(body = "https://matrix.org?foo=bar&bar=foo")
|
createEvent(body = "https://matrix.org?foo=bar&bar=foo")
|
||||||
|
.toFakeTimelineEvent()
|
||||||
.let { urlsExtractor.extract(it) }
|
.let { urlsExtractor.extract(it) }
|
||||||
.let { result ->
|
.let { result ->
|
||||||
result.size shouldBeEqualTo 1
|
result.size shouldBeEqualTo 1
|
||||||
|
@ -80,6 +87,7 @@ internal class UrlsExtractorTest : InstrumentedTest {
|
||||||
@Test
|
@Test
|
||||||
fun oneUrlInlinedTest() {
|
fun oneUrlInlinedTest() {
|
||||||
createEvent(body = "Hello https://matrix.org, how are you?")
|
createEvent(body = "Hello https://matrix.org, how are you?")
|
||||||
|
.toFakeTimelineEvent()
|
||||||
.let { urlsExtractor.extract(it) }
|
.let { urlsExtractor.extract(it) }
|
||||||
.let { result ->
|
.let { result ->
|
||||||
result.size shouldBeEqualTo 1
|
result.size shouldBeEqualTo 1
|
||||||
|
@ -90,6 +98,7 @@ internal class UrlsExtractorTest : InstrumentedTest {
|
||||||
@Test
|
@Test
|
||||||
fun twoUrlsTest() {
|
fun twoUrlsTest() {
|
||||||
createEvent(body = "https://matrix.org https://example.org")
|
createEvent(body = "https://matrix.org https://example.org")
|
||||||
|
.toFakeTimelineEvent()
|
||||||
.let { urlsExtractor.extract(it) }
|
.let { urlsExtractor.extract(it) }
|
||||||
.let { result ->
|
.let { result ->
|
||||||
result.size shouldBeEqualTo 2
|
result.size shouldBeEqualTo 2
|
||||||
|
@ -99,10 +108,26 @@ internal class UrlsExtractorTest : InstrumentedTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEvent(body: String): Event = Event(
|
private fun createEvent(body: String): Event = Event(
|
||||||
|
eventId = "!fake",
|
||||||
type = EventType.MESSAGE,
|
type = EventType.MESSAGE,
|
||||||
content = MessageTextContent(
|
content = MessageTextContent(
|
||||||
msgType = MessageType.MSGTYPE_TEXT,
|
msgType = MessageType.MSGTYPE_TEXT,
|
||||||
body = body
|
body = body
|
||||||
).toContent()
|
).toContent()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun Event.toFakeTimelineEvent(): TimelineEvent {
|
||||||
|
return TimelineEvent(
|
||||||
|
root = this,
|
||||||
|
localId = 0L,
|
||||||
|
eventId = eventId!!,
|
||||||
|
displayIndex = 0,
|
||||||
|
senderInfo = SenderInfo(
|
||||||
|
userId = "",
|
||||||
|
displayName = null,
|
||||||
|
isUniqueDisplayName = true,
|
||||||
|
avatarUrl = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,8 +66,8 @@ class TimelineForwardPaginationTest : InstrumentedTest {
|
||||||
numberOfMessagesToSend)
|
numberOfMessagesToSend)
|
||||||
|
|
||||||
// Alice clear the cache
|
// Alice clear the cache
|
||||||
commonTestHelper.doSync<Unit> {
|
commonTestHelper.runBlockingTest {
|
||||||
aliceSession.clearCache(it)
|
aliceSession.clearCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
// And restarts the sync
|
// And restarts the sync
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.matrix.android.sdk.api.auth
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class TokenBasedAuth(
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a session identifier that the client must pass back to the homeserver,
|
||||||
|
* if one is provided, in subsequent attempts to authenticate in the same API call.
|
||||||
|
*/
|
||||||
|
@Json(name = "session")
|
||||||
|
override val session: String? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A client may receive a login token via some external service, such as email or SMS.
|
||||||
|
* Note that a login token is separate from an access token, the latter providing general authentication to various API endpoints.
|
||||||
|
*/
|
||||||
|
@Json(name = "token")
|
||||||
|
val token: String? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The txn_id should be a random string generated by the client for the request.
|
||||||
|
* The same txn_id should be used if retrying the request.
|
||||||
|
* The txn_id may be used by the server to disallow other devices from using the token,
|
||||||
|
* thus providing "single use" tokens while still allowing the device to retry the request.
|
||||||
|
* This would be done by tying the token to the txn_id server side, as well as potentially invalidating
|
||||||
|
* the token completely once the device has successfully logged in
|
||||||
|
* (e.g. when we receive a request from the newly provisioned access_token).
|
||||||
|
*/
|
||||||
|
@Json(name = "txn_id")
|
||||||
|
val transactionId: String? = null,
|
||||||
|
|
||||||
|
// registration information
|
||||||
|
@Json(name = "type")
|
||||||
|
val type: String? = LoginFlowTypes.TOKEN
|
||||||
|
|
||||||
|
) : UIABaseAuth {
|
||||||
|
override fun hasAuthInfo() = token != null
|
||||||
|
|
||||||
|
override fun copyWithSession(session: String) = this.copy(session = session)
|
||||||
|
|
||||||
|
override fun asMap(): Map<String, *> = mapOf(
|
||||||
|
"session" to session,
|
||||||
|
"token" to token,
|
||||||
|
"transactionId" to transactionId,
|
||||||
|
"type" to type
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.auth
|
||||||
|
|
||||||
|
interface UIABaseAuth {
|
||||||
|
/**
|
||||||
|
* This is a session identifier that the client must pass back to the homeserver,
|
||||||
|
* if one is provided, in subsequent attempts to authenticate in the same API call.
|
||||||
|
*/
|
||||||
|
val session: String?
|
||||||
|
|
||||||
|
fun hasAuthInfo(): Boolean
|
||||||
|
|
||||||
|
fun copyWithSession(session: String): UIABaseAuth
|
||||||
|
|
||||||
|
fun asMap() : Map<String, *>
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.auth
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some API endpoints require authentication that interacts with the user.
|
||||||
|
* The homeserver may provide many different ways of authenticating, such as user/password auth, login via a social network (OAuth2),
|
||||||
|
* login by confirming a token sent to their email address, etc.
|
||||||
|
*
|
||||||
|
* The process takes the form of one or more 'stages'.
|
||||||
|
* At each stage the client submits a set of data for a given authentication type and awaits a response from the server,
|
||||||
|
* which will either be a final success or a request to perform an additional stage.
|
||||||
|
* This exchange continues until the final success.
|
||||||
|
*
|
||||||
|
* For each endpoint, a server offers one or more 'flows' that the client can use to authenticate itself.
|
||||||
|
* Each flow comprises a series of stages, as described above.
|
||||||
|
* The client is free to choose which flow it follows, however the flow's stages must be completed in order.
|
||||||
|
* Failing to follow the flows in order must result in an HTTP 401 response.
|
||||||
|
* When all stages in a flow are complete, authentication is complete and the API call succeeds.
|
||||||
|
*/
|
||||||
|
interface UserInteractiveAuthInterceptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the API needs additional auth, this will be called.
|
||||||
|
* Implementation should check the flows from flow response and act accordingly.
|
||||||
|
* Updated auth should be provided using promise.resume, this allow implementation to perform
|
||||||
|
* an async operation (prompt for user password, open sso fallback) and then resume initial API call when done.
|
||||||
|
*/
|
||||||
|
fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>)
|
||||||
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.internal.crypto.model.rest
|
package org.matrix.android.sdk.api.auth
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
@ -27,7 +27,7 @@ data class UserPasswordAuth(
|
||||||
|
|
||||||
// device device session id
|
// device device session id
|
||||||
@Json(name = "session")
|
@Json(name = "session")
|
||||||
val session: String? = null,
|
override val session: String? = null,
|
||||||
|
|
||||||
// registration information
|
// registration information
|
||||||
@Json(name = "type")
|
@Json(name = "type")
|
||||||
|
@ -38,4 +38,16 @@ data class UserPasswordAuth(
|
||||||
|
|
||||||
@Json(name = "password")
|
@Json(name = "password")
|
||||||
val password: String? = null
|
val password: String? = null
|
||||||
)
|
) : UIABaseAuth {
|
||||||
|
|
||||||
|
override fun hasAuthInfo() = password != null
|
||||||
|
|
||||||
|
override fun copyWithSession(session: String) = this.copy(session = session)
|
||||||
|
|
||||||
|
override fun asMap(): Map<String, *> = mapOf(
|
||||||
|
"session" to session,
|
||||||
|
"user" to user,
|
||||||
|
"password" to password,
|
||||||
|
"type" to type
|
||||||
|
)
|
||||||
|
}
|
|
@ -38,15 +38,24 @@ data class SsoIdentityProvider(
|
||||||
* If present then it must be an HTTPS URL to an image resource.
|
* If present then it must be an HTTPS URL to an image resource.
|
||||||
* This should be hosted by the homeserver service provider to not leak the client's IP address unnecessarily.
|
* This should be hosted by the homeserver service provider to not leak the client's IP address unnecessarily.
|
||||||
*/
|
*/
|
||||||
@Json(name = "icon") val iconUrl: String?
|
@Json(name = "icon") val iconUrl: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `brand` field is **optional**. It allows the client to style the login
|
||||||
|
* button to suit a particular brand. It should be a string matching the
|
||||||
|
* "Common namespaced identifier grammar" as defined in
|
||||||
|
* [MSC2758](https://github.com/matrix-org/matrix-doc/pull/2758).
|
||||||
|
*/
|
||||||
|
@Json(name = "brand") val brand: String?
|
||||||
|
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// Not really defined by the spec, but we may define some ids here
|
const val BRAND_GOOGLE = "org.matrix.google"
|
||||||
const val ID_GOOGLE = "google"
|
const val BRAND_GITHUB = "org.matrix.github"
|
||||||
const val ID_GITHUB = "github"
|
const val BRAND_APPLE = "org.matrix.apple"
|
||||||
const val ID_APPLE = "apple"
|
const val BRAND_FACEBOOK = "org.matrix.facebook"
|
||||||
const val ID_FACEBOOK = "facebook"
|
const val BRAND_TWITTER = "org.matrix.twitter"
|
||||||
const val ID_TWITTER = "twitter"
|
const val BRAND_GITLAB = "org.matrix.gitlab"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.auth.registration
|
package org.matrix.android.sdk.api.auth.registration
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||||
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
|
||||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
|
||||||
import org.matrix.android.sdk.api.auth.registration.TermPolicies
|
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.internal.auth.data.InteractiveAuthenticationFlow
|
import org.matrix.android.sdk.internal.auth.data.InteractiveAuthenticationFlow
|
||||||
|
|
||||||
|
@ -109,3 +106,8 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult {
|
||||||
|
|
||||||
return FlowResult(missingStage, completedStage)
|
return FlowResult(missingStage, completedStage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun RegistrationFlowResponse.nextUncompletedStage(flowIndex: Int = 0): String? {
|
||||||
|
val completed = completedStages ?: emptyList()
|
||||||
|
return flows?.getOrNull(flowIndex)?.stages?.firstOrNull { completed.contains(it).not() }
|
||||||
|
}
|
|
@ -16,8 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.failure
|
package org.matrix.android.sdk.api.failure
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.internal.auth.registration.RegistrationFlowResponse
|
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
@ -43,6 +43,12 @@ fun Throwable.isInvalidPassword(): Boolean {
|
||||||
&& error.message == "Invalid password"
|
&& error.message == "Invalid password"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Throwable.isInvalidUIAAuth(): Boolean {
|
||||||
|
return this is Failure.ServerError
|
||||||
|
&& error.code == MatrixError.M_FORBIDDEN
|
||||||
|
&& error.flows != null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
|
* Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
|
||||||
*/
|
*/
|
||||||
|
@ -53,6 +59,16 @@ fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
|
||||||
.adapter(RegistrationFlowResponse::class.java)
|
.adapter(RegistrationFlowResponse::class.java)
|
||||||
.fromJson(this.errorBody)
|
.fromJson(this.errorBody)
|
||||||
}
|
}
|
||||||
|
} else if (this is Failure.ServerError && this.httpCode == 401 && this.error.code == MatrixError.M_FORBIDDEN) {
|
||||||
|
// This happens when the submission for this stage was bad (like bad password)
|
||||||
|
if (this.error.session != null && this.error.flows != null) {
|
||||||
|
RegistrationFlowResponse(
|
||||||
|
flows = this.error.flows,
|
||||||
|
session = this.error.session,
|
||||||
|
completedStages = this.error.completedStages,
|
||||||
|
params = this.error.params
|
||||||
|
)
|
||||||
|
} else null
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.failure
|
package org.matrix.android.sdk.api.failure
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.internal.auth.registration.RegistrationFlowResponse
|
|
||||||
import org.matrix.android.sdk.internal.network.ssl.Fingerprint
|
import org.matrix.android.sdk.internal.network.ssl.Fingerprint
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ package org.matrix.android.sdk.api.failure
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
import org.matrix.android.sdk.internal.auth.data.InteractiveAuthenticationFlow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This data class holds the error defined by the matrix specifications.
|
* This data class holds the error defined by the matrix specifications.
|
||||||
|
@ -42,7 +44,17 @@ data class MatrixError(
|
||||||
@Json(name = "soft_logout") val isSoftLogout: Boolean = false,
|
@Json(name = "soft_logout") val isSoftLogout: Boolean = false,
|
||||||
// For M_INVALID_PEPPER
|
// For M_INVALID_PEPPER
|
||||||
// {"error": "pepper does not match 'erZvr'", "lookup_pepper": "pQgMS", "algorithm": "sha256", "errcode": "M_INVALID_PEPPER"}
|
// {"error": "pepper does not match 'erZvr'", "lookup_pepper": "pQgMS", "algorithm": "sha256", "errcode": "M_INVALID_PEPPER"}
|
||||||
@Json(name = "lookup_pepper") val newLookupPepper: String? = null
|
@Json(name = "lookup_pepper") val newLookupPepper: String? = null,
|
||||||
|
|
||||||
|
// For M_FORBIDDEN UIA
|
||||||
|
@Json(name = "session")
|
||||||
|
val session: String? = null,
|
||||||
|
@Json(name = "completed")
|
||||||
|
val completedStages: List<String>? = null,
|
||||||
|
@Json(name = "flows")
|
||||||
|
val flows: List<InteractiveAuthenticationFlow>? = null,
|
||||||
|
@Json(name = "params")
|
||||||
|
val params: JsonDict? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -245,6 +245,8 @@ interface Session :
|
||||||
|
|
||||||
val sharedSecretStorageService: SharedSecretStorageService
|
val sharedSecretStorageService: SharedSecretStorageService
|
||||||
|
|
||||||
|
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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.account
|
package org.matrix.android.sdk.api.session.account
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to manage the account. It's implemented at the session level.
|
* This interface defines methods to manage the account. It's implemented at the session level.
|
||||||
*/
|
*/
|
||||||
|
@ -43,5 +45,5 @@ interface AccountService {
|
||||||
* @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see
|
* @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see
|
||||||
* an incomplete view of conversations
|
* an incomplete view of conversations
|
||||||
*/
|
*/
|
||||||
suspend fun deactivateAccount(password: String, eraseAllData: Boolean)
|
suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.cache
|
package org.matrix.android.sdk.api.session.cache
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines a method to clear the cache. It's implemented at the session level.
|
* This interface defines a method to clear the cache. It's implemented at the session level.
|
||||||
*/
|
*/
|
||||||
|
@ -26,5 +24,5 @@ interface CacheService {
|
||||||
/**
|
/**
|
||||||
* Clear the whole cached data, except credentials. Once done, the sync has to be restarted by the sdk user.
|
* Clear the whole cached data, except credentials. Once done, the sync has to be restarted by the sdk user.
|
||||||
*/
|
*/
|
||||||
fun clearCache(callback: MatrixCallback<Unit>)
|
suspend fun clearCache()
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
||||||
|
@ -53,7 +54,7 @@ interface CryptoService {
|
||||||
|
|
||||||
fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>)
|
fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>)
|
fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>)
|
fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,10 @@ package org.matrix.android.sdk.api.session.crypto.crosssigning
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustResult
|
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustResult
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
|
import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
|
||||||
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
||||||
|
|
||||||
interface CrossSigningService {
|
interface CrossSigningService {
|
||||||
|
@ -40,7 +40,7 @@ interface CrossSigningService {
|
||||||
* Initialize cross signing for this user.
|
* Initialize cross signing for this user.
|
||||||
* Users needs to enter credentials
|
* Users needs to enter credentials
|
||||||
*/
|
*/
|
||||||
fun initializeCrossSigning(authParams: UserPasswordAuth?,
|
fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?,
|
||||||
callback: MatrixCallback<Unit>)
|
callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
|
fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
|
||||||
|
|
|
@ -17,15 +17,16 @@
|
||||||
package org.matrix.android.sdk.api.session.media
|
package org.matrix.android.sdk.api.session.media
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.cache.CacheStrategy
|
import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
|
||||||
interface MediaService {
|
interface MediaService {
|
||||||
/**
|
/**
|
||||||
* Extract URLs from an Event.
|
* Extract URLs from a TimelineEvent.
|
||||||
* @return the list of URLs contains in the body of the Event. It does not mean that URLs in this list have UrlPreview data
|
* @param event TimelineEvent to extract the URL from.
|
||||||
|
* @return the list of URLs contains in the body of the TimelineEvent. It does not mean that URLs in this list have UrlPreview data
|
||||||
*/
|
*/
|
||||||
fun extractUrls(event: Event): List<String>
|
fun extractUrls(event: TimelineEvent): List<String>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Raw Url Preview data from the homeserver. There is no cache management for this request
|
* Get Raw Url Preview data from the homeserver. There is no cache management for this request
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.matrix.android.sdk.api.session.profile
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
@ -107,8 +108,7 @@ interface ProfileService {
|
||||||
* Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid
|
* Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid
|
||||||
*/
|
*/
|
||||||
fun finalizeAddingThreePid(threePid: ThreePid,
|
fun finalizeAddingThreePid(threePid: ThreePid,
|
||||||
uiaSession: String?,
|
userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
|
||||||
accountPassword: String?,
|
|
||||||
matrixCallback: MatrixCallback<Unit>): Cancelable
|
matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -89,6 +89,17 @@ data class TimelineEvent(
|
||||||
*/
|
*/
|
||||||
fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null
|
fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest known eventId for an edited event, or the eventId for an Event which has not been edited
|
||||||
|
*/
|
||||||
|
fun TimelineEvent.getLatestEventId(): String {
|
||||||
|
return annotations
|
||||||
|
?.editSummary
|
||||||
|
?.sourceEvents
|
||||||
|
?.lastOrNull()
|
||||||
|
?: eventId
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the relation content if any
|
* Get the relation content if any
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,9 +16,7 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.signout
|
package org.matrix.android.sdk.api.session.signout
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines a method to sign out, or to renew the token. It's implemented at the session level.
|
* This interface defines a method to sign out, or to renew the token. It's implemented at the session level.
|
||||||
|
@ -29,19 +27,16 @@ interface SignOutService {
|
||||||
* Ask the homeserver for a new access token.
|
* Ask the homeserver for a new access token.
|
||||||
* The same deviceId will be used
|
* The same deviceId will be used
|
||||||
*/
|
*/
|
||||||
fun signInAgain(password: String,
|
suspend fun signInAgain(password: String)
|
||||||
callback: MatrixCallback<Unit>): Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the session with credentials received after SSO
|
* Update the session with credentials received after SSO
|
||||||
*/
|
*/
|
||||||
fun updateCredentials(credentials: Credentials,
|
suspend fun updateCredentials(credentials: Credentials)
|
||||||
callback: MatrixCallback<Unit>): Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign out, and release the session, clear all the session data, including crypto data
|
* Sign out, and release the session, clear all the session data, including crypto data
|
||||||
* @param signOutFromHomeserver true if the sign out request has to be done
|
* @param signOutFromHomeserver true if the sign out request has to be done
|
||||||
*/
|
*/
|
||||||
fun signOut(signOutFromHomeserver: Boolean,
|
suspend fun signOut(signOutFromHomeserver: Boolean)
|
||||||
callback: MatrixCallback<Unit>): Cancelable
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,3 +36,6 @@ internal const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
|
||||||
internal const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
|
internal const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
|
||||||
|
|
||||||
internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
||||||
|
|
||||||
|
// Ref: https://matrix.org/docs/spec/client_server/r0.6.1#single-sign-on
|
||||||
|
internal const val SSO_UIA_FALLBACK_PATH = "/_matrix/client/r0/auth/m.login.sso/fallback/web"
|
||||||
|
|
|
@ -43,5 +43,6 @@ internal data class LoginFlow(
|
||||||
* See MSC #2858
|
* See MSC #2858
|
||||||
*/
|
*/
|
||||||
@Json(name = "org.matrix.msc2858.identity_providers")
|
@Json(name = "org.matrix.msc2858.identity_providers")
|
||||||
val ssoIdentityProvider: List<SsoIdentityProvider>?
|
val ssoIdentityProvider: List<SsoIdentityProvider>? = null
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||||
|
import org.matrix.android.sdk.api.auth.registration.toFlowResult
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError
|
import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.auth.registration
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
|
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
|
||||||
|
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||||
|
import timber.log.Timber
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveAuthInterceptor, retryBlock: suspend (UIABaseAuth) -> Unit): Boolean {
|
||||||
|
Timber.d("## UIA: check error ${failure.message}")
|
||||||
|
val flowResponse = failure.toRegistrationFlowResponse()
|
||||||
|
?: return false.also {
|
||||||
|
Timber.d("## UIA: not a UIA error")
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.d("## UIA: error can be passed to interceptor")
|
||||||
|
Timber.d("## UIA: type = ${flowResponse.flows}")
|
||||||
|
|
||||||
|
Timber.d("## UIA: delegate to interceptor...")
|
||||||
|
val authUpdate = try {
|
||||||
|
suspendCoroutine<UIABaseAuth> { continuation ->
|
||||||
|
interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation)
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.w(failure, "## UIA: failed to participate")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.d("## UIA: updated auth $authUpdate")
|
||||||
|
return try {
|
||||||
|
retryBlock(authUpdate)
|
||||||
|
true
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
handleUIA(failure, interceptor, retryBlock)
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
|
@ -207,9 +208,9 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
override fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) {
|
||||||
deleteDeviceTask
|
deleteDeviceTask
|
||||||
.configureWith(DeleteDeviceTask.Params(deviceId)) {
|
.configureWith(DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null)) {
|
||||||
this.executionThread = TaskThread.CRYPTO
|
this.executionThread = TaskThread.CRYPTO
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,30 +19,30 @@ package org.matrix.android.sdk.internal.crypto.crosssigning
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.work.BackoffPolicy
|
import androidx.work.BackoffPolicy
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder
|
import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask
|
import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
|
import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionId
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
|
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.task.TaskThread
|
import org.matrix.android.sdk.internal.task.TaskThread
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
import org.matrix.android.sdk.internal.task.configureWith
|
||||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
|
||||||
import org.matrix.android.sdk.internal.di.SessionId
|
|
||||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
import org.matrix.olm.OlmPkSigning
|
import org.matrix.olm.OlmPkSigning
|
||||||
import org.matrix.olm.OlmUtility
|
import org.matrix.olm.OlmUtility
|
||||||
|
@ -61,7 +61,10 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val cryptoCoroutineScope: CoroutineScope,
|
private val cryptoCoroutineScope: CoroutineScope,
|
||||||
private val workManagerProvider: WorkManagerProvider) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
|
private val workManagerProvider: WorkManagerProvider,
|
||||||
|
private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository
|
||||||
|
) : CrossSigningService,
|
||||||
|
DeviceListManager.UserDevicesUpdateListener {
|
||||||
|
|
||||||
private var olmUtility: OlmUtility? = null
|
private var olmUtility: OlmUtility? = null
|
||||||
|
|
||||||
|
@ -147,11 +150,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
* - Sign the keys and upload them
|
* - Sign the keys and upload them
|
||||||
* - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures
|
* - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures
|
||||||
*/
|
*/
|
||||||
override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback<Unit>) {
|
override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback<Unit>) {
|
||||||
Timber.d("## CrossSigning initializeCrossSigning")
|
Timber.d("## CrossSigning initializeCrossSigning")
|
||||||
|
|
||||||
val params = InitializeCrossSigningTask.Params(
|
val params = InitializeCrossSigningTask.Params(
|
||||||
authParams = authParams
|
interactiveAuthInterceptor = uiaInterceptor
|
||||||
)
|
)
|
||||||
initializeCrossSigningTask.configureWith(params) {
|
initializeCrossSigningTask.configureWith(params) {
|
||||||
this.callbackThread = TaskThread.CRYPTO
|
this.callbackThread = TaskThread.CRYPTO
|
||||||
|
@ -689,7 +692,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted))
|
return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkDeviceTrust(myKeys: MXCrossSigningInfo?, otherKeys: MXCrossSigningInfo?, otherDevice: CryptoDeviceInfo) : DeviceTrustResult {
|
fun checkDeviceTrust(myKeys: MXCrossSigningInfo?, otherKeys: MXCrossSigningInfo?, otherDevice: CryptoDeviceInfo): DeviceTrustResult {
|
||||||
val locallyTrusted = otherDevice.trustLevel?.isLocallyVerified()
|
val locallyTrusted = otherDevice.trustLevel?.isLocallyVerified()
|
||||||
myKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId))
|
myKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId))
|
||||||
|
|
||||||
|
@ -747,8 +750,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUsersDeviceUpdate(userIds: List<String>) {
|
override fun onUsersDeviceUpdate(userIds: List<String>) {
|
||||||
Timber.d("## CrossSigning - onUsersDeviceUpdate for $userIds")
|
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${userIds.size} users: $userIds")
|
||||||
val workerParams = UpdateTrustWorker.Params(sessionId = sessionId, updatedUserIds = userIds)
|
val workerParams = UpdateTrustWorker.Params(
|
||||||
|
sessionId = sessionId,
|
||||||
|
filename = updateTrustWorkerDataRepository.createParam(userIds)
|
||||||
|
)
|
||||||
val workerData = WorkerParamsFactory.toData(workerParams)
|
val workerData = WorkerParamsFactory.toData(workerParams)
|
||||||
|
|
||||||
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<UpdateTrustWorker>()
|
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<UpdateTrustWorker>()
|
||||||
|
|
|
@ -55,7 +55,11 @@ internal class UpdateTrustWorker(context: Context,
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
override val sessionId: String,
|
override val sessionId: String,
|
||||||
override val lastFailureMessage: String? = null,
|
override val lastFailureMessage: String? = null,
|
||||||
val updatedUserIds: List<String>
|
// Kept for compatibility, but not used anymore (can be used for pending Worker)
|
||||||
|
val updatedUserIds: List<String>? = null,
|
||||||
|
// Passing a long list of userId can break the Work Manager due to data size limitation.
|
||||||
|
// so now we use a temporary file to store the data
|
||||||
|
val filename: String? = null
|
||||||
) : SessionWorkerParams
|
) : SessionWorkerParams
|
||||||
|
|
||||||
@Inject lateinit var crossSigningService: DefaultCrossSigningService
|
@Inject lateinit var crossSigningService: DefaultCrossSigningService
|
||||||
|
@ -64,6 +68,7 @@ internal class UpdateTrustWorker(context: Context,
|
||||||
@CryptoDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
|
@CryptoDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
|
||||||
@UserId @Inject lateinit var myUserId: String
|
@UserId @Inject lateinit var myUserId: String
|
||||||
@Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper
|
@Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper
|
||||||
|
@Inject lateinit var updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository
|
||||||
@SessionDatabase @Inject lateinit var sessionRealmConfiguration: RealmConfiguration
|
@SessionDatabase @Inject lateinit var sessionRealmConfiguration: RealmConfiguration
|
||||||
|
|
||||||
// @Inject lateinit var roomSummaryUpdater: RoomSummaryUpdater
|
// @Inject lateinit var roomSummaryUpdater: RoomSummaryUpdater
|
||||||
|
@ -74,7 +79,17 @@ internal class UpdateTrustWorker(context: Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun doSafeWork(params: Params): Result {
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
var userList = params.updatedUserIds
|
var userList = params.filename
|
||||||
|
?.let { updateTrustWorkerDataRepository.getParam(it) }
|
||||||
|
?.userIds
|
||||||
|
?: params.updatedUserIds.orEmpty()
|
||||||
|
|
||||||
|
if (userList.isEmpty()) {
|
||||||
|
// This should not happen, but let's avoid go further in case of empty list
|
||||||
|
cleanup(params)
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
|
||||||
// Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user,
|
// Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user,
|
||||||
// or a new device?) So we check all again :/
|
// or a new device?) So we check all again :/
|
||||||
|
|
||||||
|
@ -213,9 +228,15 @@ internal class UpdateTrustWorker(context: Context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanup(params)
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun cleanup(params: Params) {
|
||||||
|
params.filename
|
||||||
|
?.let { updateTrustWorkerDataRepository.delete(it) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateCrossSigningKeysTrust(realm: Realm, userId: String, verified: Boolean) {
|
private fun updateCrossSigningKeysTrust(realm: Realm, userId: String, verified: Boolean) {
|
||||||
val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java)
|
val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java)
|
||||||
.equalTo(CrossSigningInfoEntityFields.USER_ID, userId)
|
.equalTo(CrossSigningInfoEntityFields.USER_ID, userId)
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.crypto.crosssigning
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
||||||
|
import java.io.File
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class UpdateTrustWorkerData(
|
||||||
|
@Json(name = "userIds")
|
||||||
|
val userIds: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
internal class UpdateTrustWorkerDataRepository @Inject constructor(
|
||||||
|
@SessionFilesDirectory parentDir: File
|
||||||
|
) {
|
||||||
|
private val workingDirectory = File(parentDir, "tw")
|
||||||
|
private val jsonAdapter = MoshiProvider.providesMoshi().adapter(UpdateTrustWorkerData::class.java)
|
||||||
|
|
||||||
|
// Return the path of the created file
|
||||||
|
fun createParam(userIds: List<String>): String {
|
||||||
|
val filename = "${UUID.randomUUID()}.json"
|
||||||
|
workingDirectory.mkdirs()
|
||||||
|
val file = File(workingDirectory, filename)
|
||||||
|
|
||||||
|
UpdateTrustWorkerData(userIds = userIds)
|
||||||
|
.let { jsonAdapter.toJson(it) }
|
||||||
|
.let { file.writeText(it) }
|
||||||
|
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getParam(filename: String): UpdateTrustWorkerData? {
|
||||||
|
return File(workingDirectory, filename)
|
||||||
|
.takeIf { it.exists() }
|
||||||
|
?.readText()
|
||||||
|
?.let { jsonAdapter.fromJson(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(filename: String) {
|
||||||
|
tryOrNull("Unable to delete $filename") {
|
||||||
|
File(workingDirectory, filename).delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.crypto.model.rest
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||||
|
|
||||||
|
data class DefaultBaseAuth(
|
||||||
|
/**
|
||||||
|
* This is a session identifier that the client must pass back to the homeserver,
|
||||||
|
* if one is provided, in subsequent attempts to authenticate in the same API call.
|
||||||
|
*/
|
||||||
|
override val session: String? = null
|
||||||
|
|
||||||
|
) : UIABaseAuth {
|
||||||
|
override fun hasAuthInfo() = true
|
||||||
|
|
||||||
|
override fun copyWithSession(session: String) = this.copy(session = session)
|
||||||
|
|
||||||
|
override fun asMap(): Map<String, *> = mapOf("session" to session)
|
||||||
|
}
|
|
@ -24,5 +24,5 @@ import com.squareup.moshi.JsonClass
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class DeleteDeviceParams(
|
internal data class DeleteDeviceParams(
|
||||||
@Json(name = "auth")
|
@Json(name = "auth")
|
||||||
val userPasswordAuth: UserPasswordAuth? = null
|
val auth: Map<String, *>? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -30,5 +30,5 @@ internal data class UploadSigningKeysBody(
|
||||||
val userSigningKey: RestKeyInfo? = null,
|
val userSigningKey: RestKeyInfo? = null,
|
||||||
|
|
||||||
@Json(name = "auth")
|
@Json(name = "auth")
|
||||||
val auth: UserPasswordAuth? = null
|
val auth: Map<String, *>? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,18 +16,22 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.tasks
|
package org.matrix.android.sdk.internal.crypto.tasks
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
|
import org.matrix.android.sdk.internal.auth.registration.handleUIA
|
||||||
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
|
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
|
||||||
|
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
|
internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val deviceId: String
|
val deviceId: String,
|
||||||
|
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor?,
|
||||||
|
val userAuthParam: UIABaseAuth?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,12 +43,17 @@ internal class DefaultDeleteDeviceTask @Inject constructor(
|
||||||
override suspend fun execute(params: DeleteDeviceTask.Params) {
|
override suspend fun execute(params: DeleteDeviceTask.Params) {
|
||||||
try {
|
try {
|
||||||
executeRequest<Unit>(globalErrorReceiver) {
|
executeRequest<Unit>(globalErrorReceiver) {
|
||||||
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
|
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams(params.userAuthParam?.asMap()))
|
||||||
}
|
}
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
throw throwable.toRegistrationFlowResponse()
|
if (params.userInteractiveAuthInterceptor == null
|
||||||
?.let { Failure.RegistrationFlowError(it) }
|
|| !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
|
||||||
?: throwable
|
execute(params.copy(userAuthParam = auth))
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Timber.d("## UIA: propagate failure")
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.crypto.tasks
|
||||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||||
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
|
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
@ -44,12 +44,12 @@ internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(
|
||||||
return executeRequest(globalErrorReceiver) {
|
return executeRequest(globalErrorReceiver) {
|
||||||
apiCall = cryptoApi.deleteDevice(params.deviceId,
|
apiCall = cryptoApi.deleteDevice(params.deviceId,
|
||||||
DeleteDeviceParams(
|
DeleteDeviceParams(
|
||||||
userPasswordAuth = UserPasswordAuth(
|
auth = UserPasswordAuth(
|
||||||
type = LoginFlowTypes.PASSWORD,
|
type = LoginFlowTypes.PASSWORD,
|
||||||
session = params.authSession,
|
session = params.authSession,
|
||||||
user = userId,
|
user = userId,
|
||||||
password = params.password
|
password = params.password
|
||||||
)
|
).asMap()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package org.matrix.android.sdk.internal.crypto.tasks
|
package org.matrix.android.sdk.internal.crypto.tasks
|
||||||
|
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
|
import org.matrix.android.sdk.internal.auth.registration.handleUIA
|
||||||
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
||||||
import org.matrix.android.sdk.internal.crypto.MyDeviceInfoHolder
|
import org.matrix.android.sdk.internal.crypto.MyDeviceInfoHolder
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.canonicalSignable
|
import org.matrix.android.sdk.internal.crypto.crosssigning.canonicalSignable
|
||||||
|
@ -24,7 +26,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey
|
import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey
|
||||||
import org.matrix.android.sdk.internal.crypto.model.KeyUsage
|
import org.matrix.android.sdk.internal.crypto.model.KeyUsage
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder
|
import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||||
|
@ -34,7 +35,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
internal interface InitializeCrossSigningTask : Task<InitializeCrossSigningTask.Params, InitializeCrossSigningTask.Result> {
|
internal interface InitializeCrossSigningTask : Task<InitializeCrossSigningTask.Params, InitializeCrossSigningTask.Result> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val authParams: UserPasswordAuth?
|
val interactiveAuthInterceptor: UserInteractiveAuthInterceptor?
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Result(
|
data class Result(
|
||||||
|
@ -117,10 +118,21 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
|
||||||
.key(sskPublicKey)
|
.key(sskPublicKey)
|
||||||
.signature(userId, masterPublicKey, signedSSK)
|
.signature(userId, masterPublicKey, signedSSK)
|
||||||
.build(),
|
.build(),
|
||||||
userPasswordAuth = params.authParams
|
userAuthParam = null
|
||||||
|
// userAuthParam = params.authParams
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
uploadSigningKeysTask.execute(uploadSigningKeysParams)
|
uploadSigningKeysTask.execute(uploadSigningKeysParams)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
if (params.interactiveAuthInterceptor == null
|
||||||
|
|| !handleUIA(failure, params.interactiveAuthInterceptor) { authUpdate ->
|
||||||
|
uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
|
||||||
|
}) {
|
||||||
|
Timber.d("## UIA: propagate failure")
|
||||||
|
throw failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sign the current device with SSK
|
// Sign the current device with SSK
|
||||||
val uploadSignatureQueryBuilder = UploadSignatureQueryBuilder()
|
val uploadSignatureQueryBuilder = UploadSignatureQueryBuilder()
|
||||||
|
|
|
@ -16,14 +16,12 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.tasks
|
package org.matrix.android.sdk.internal.crypto.tasks
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
|
|
||||||
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey
|
import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
|
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
|
||||||
|
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody
|
import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.toRest
|
import org.matrix.android.sdk.internal.crypto.model.toRest
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
@ -39,15 +37,9 @@ internal interface UploadSigningKeysTask : Task<UploadSigningKeysTask.Params, Un
|
||||||
// the SSK
|
// the SSK
|
||||||
val selfSignedKey: CryptoCrossSigningKey,
|
val selfSignedKey: CryptoCrossSigningKey,
|
||||||
/**
|
/**
|
||||||
* - If null:
|
* Authorisation info (User Interactive flow)
|
||||||
* - no retry will be performed
|
|
||||||
* - If not null, it may or may not contain a sessionId:
|
|
||||||
* - If sessionId is null:
|
|
||||||
* - password should not be null: the task will perform a first request to get a sessionId, and then a second one
|
|
||||||
* - If sessionId is not null:
|
|
||||||
* - password should not be null as well, and no retry will be performed
|
|
||||||
*/
|
*/
|
||||||
val userPasswordAuth: UserPasswordAuth?
|
val userAuthParam: UIABaseAuth?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,31 +51,13 @@ internal class DefaultUploadSigningKeysTask @Inject constructor(
|
||||||
) : UploadSigningKeysTask {
|
) : UploadSigningKeysTask {
|
||||||
|
|
||||||
override suspend fun execute(params: UploadSigningKeysTask.Params) {
|
override suspend fun execute(params: UploadSigningKeysTask.Params) {
|
||||||
val paramsHaveSessionId = params.userPasswordAuth?.session != null
|
|
||||||
|
|
||||||
val uploadQuery = UploadSigningKeysBody(
|
val uploadQuery = UploadSigningKeysBody(
|
||||||
masterKey = params.masterKey.toRest(),
|
masterKey = params.masterKey.toRest(),
|
||||||
userSigningKey = params.userKey.toRest(),
|
userSigningKey = params.userKey.toRest(),
|
||||||
selfSigningKey = params.selfSignedKey.toRest(),
|
selfSigningKey = params.selfSignedKey.toRest(),
|
||||||
// If sessionId is provided, use the userPasswordAuth
|
auth = params.userAuthParam?.asMap()
|
||||||
auth = params.userPasswordAuth.takeIf { paramsHaveSessionId }
|
|
||||||
)
|
)
|
||||||
try {
|
|
||||||
doRequest(uploadQuery)
|
doRequest(uploadQuery)
|
||||||
} catch (throwable: Throwable) {
|
|
||||||
val registrationFlowResponse = throwable.toRegistrationFlowResponse()
|
|
||||||
if (registrationFlowResponse != null
|
|
||||||
&& registrationFlowResponse.flows.orEmpty().any { it.stages?.contains(LoginFlowTypes.PASSWORD) == true }
|
|
||||||
&& params.userPasswordAuth?.password != null
|
|
||||||
&& !paramsHaveSessionId
|
|
||||||
) {
|
|
||||||
// Retry with authentication
|
|
||||||
doRequest(uploadQuery.copy(auth = params.userPasswordAuth.copy(session = registrationFlowResponse.session)))
|
|
||||||
} else {
|
|
||||||
// Other error
|
|
||||||
throw throwable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun doRequest(uploadQuery: UploadSigningKeysBody) {
|
private suspend fun doRequest(uploadQuery: UploadSigningKeysBody) {
|
||||||
|
|
|
@ -20,7 +20,6 @@ import androidx.annotation.MainThread
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
import org.matrix.android.sdk.api.failure.GlobalError
|
import org.matrix.android.sdk.api.failure.GlobalError
|
||||||
import org.matrix.android.sdk.api.pushrules.PushRuleService
|
import org.matrix.android.sdk.api.pushrules.PushRuleService
|
||||||
|
@ -53,6 +52,8 @@ import org.matrix.android.sdk.api.session.terms.TermsService
|
||||||
import org.matrix.android.sdk.api.session.typing.TypingUsersTracker
|
import org.matrix.android.sdk.api.session.typing.TypingUsersTracker
|
||||||
import org.matrix.android.sdk.api.session.user.UserService
|
import org.matrix.android.sdk.api.session.user.UserService
|
||||||
import org.matrix.android.sdk.api.session.widgets.WidgetService
|
import org.matrix.android.sdk.api.session.widgets.WidgetService
|
||||||
|
import org.matrix.android.sdk.api.util.appendParamToUrl
|
||||||
|
import org.matrix.android.sdk.internal.auth.SSO_UIA_FALLBACK_PATH
|
||||||
import org.matrix.android.sdk.internal.auth.SessionParamsStore
|
import org.matrix.android.sdk.internal.auth.SessionParamsStore
|
||||||
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
||||||
import org.matrix.android.sdk.internal.database.tools.RealmDebugTools
|
import org.matrix.android.sdk.internal.database.tools.RealmDebugTools
|
||||||
|
@ -217,13 +218,13 @@ internal class DefaultSession @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearCache(callback: MatrixCallback<Unit>) {
|
override suspend fun clearCache() {
|
||||||
stopSync()
|
stopSync()
|
||||||
stopAnyBackgroundSync()
|
stopAnyBackgroundSync()
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
lifecycleObservers.forEach { it.onClearCache() }
|
lifecycleObservers.forEach { it.onClearCache() }
|
||||||
}
|
}
|
||||||
cacheService.get().clearCache(callback)
|
cacheService.get().clearCache()
|
||||||
workManagerProvider.cancelAllWorks()
|
workManagerProvider.cancelAllWorks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,6 +275,18 @@ internal class DefaultSession @Inject constructor(
|
||||||
return "$myUserId - ${sessionParams.deviceId}"
|
return "$myUserId - ${sessionParams.deviceId}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getUiaSsoFallbackUrl(authenticationSessionId: String): String {
|
||||||
|
val hsBas = sessionParams.homeServerConnectionConfig
|
||||||
|
.homeServerUri
|
||||||
|
.toString()
|
||||||
|
.trim { it == '/' }
|
||||||
|
return buildString {
|
||||||
|
append(hsBas)
|
||||||
|
append(SSO_UIA_FALLBACK_PATH)
|
||||||
|
appendParamToUrl("session", authenticationSessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun logDbUsageInfo() {
|
override fun logDbUsageInfo() {
|
||||||
RealmDebugTools(realmConfiguration).logInfo("Session")
|
RealmDebugTools(realmConfiguration).logInfo("Session")
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.account
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to pass request parameters to update the password.
|
* Class to pass request parameters to update the password.
|
||||||
|
|
|
@ -18,21 +18,21 @@ package org.matrix.android.sdk.internal.session.account
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class DeactivateAccountParams(
|
internal data class DeactivateAccountParams(
|
||||||
@Json(name = "auth")
|
|
||||||
val auth: UserPasswordAuth? = null,
|
|
||||||
|
|
||||||
// Set to true to erase all data of the account
|
// Set to true to erase all data of the account
|
||||||
@Json(name = "erase")
|
@Json(name = "erase")
|
||||||
val erase: Boolean
|
val erase: Boolean,
|
||||||
|
|
||||||
|
@Json(name = "auth")
|
||||||
|
val auth: Map<String, *>? = null
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun create(userId: String, password: String, erase: Boolean): DeactivateAccountParams {
|
fun create(auth: UIABaseAuth?, erase: Boolean): DeactivateAccountParams {
|
||||||
return DeactivateAccountParams(
|
return DeactivateAccountParams(
|
||||||
auth = UserPasswordAuth(user = userId, password = password),
|
auth = auth?.asMap(),
|
||||||
erase = erase
|
erase = erase
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.account
|
package org.matrix.android.sdk.internal.session.account
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
|
import org.matrix.android.sdk.internal.auth.registration.handleUIA
|
||||||
|
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
@ -27,8 +30,9 @@ import javax.inject.Inject
|
||||||
|
|
||||||
internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> {
|
internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val password: String,
|
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
|
||||||
val eraseAllData: Boolean
|
val eraseAllData: Boolean,
|
||||||
|
val userAuthParam: UIABaseAuth? = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,12 +45,21 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
|
||||||
) : DeactivateAccountTask {
|
) : DeactivateAccountTask {
|
||||||
|
|
||||||
override suspend fun execute(params: DeactivateAccountTask.Params) {
|
override suspend fun execute(params: DeactivateAccountTask.Params) {
|
||||||
val deactivateAccountParams = DeactivateAccountParams.create(userId, params.password, params.eraseAllData)
|
val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData)
|
||||||
|
|
||||||
|
try {
|
||||||
executeRequest<Unit>(globalErrorReceiver) {
|
executeRequest<Unit>(globalErrorReceiver) {
|
||||||
apiCall = accountAPI.deactivate(deactivateAccountParams)
|
apiCall = accountAPI.deactivate(deactivateAccountParams)
|
||||||
}
|
}
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
if (!handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
|
||||||
|
execute(params.copy(userAuthParam = auth))
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Timber.d("## UIA: propagate failure")
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
|
}
|
||||||
// Logout from identity server if any, ignoring errors
|
// Logout from identity server if any, ignoring errors
|
||||||
runCatching { identityDisconnectTask.execute(Unit) }
|
runCatching { identityDisconnectTask.execute(Unit) }
|
||||||
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
|
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.account
|
package org.matrix.android.sdk.internal.session.account
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.session.account.AccountService
|
import org.matrix.android.sdk.api.session.account.AccountService
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ internal class DefaultAccountService @Inject constructor(private val changePassw
|
||||||
changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword))
|
changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deactivateAccount(password: String, eraseAllData: Boolean) {
|
override suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean) {
|
||||||
deactivateAccountTask.execute(DeactivateAccountTask.Params(password, eraseAllData))
|
deactivateAccountTask.execute(DeactivateAccountTask.Params(userInteractiveAuthInterceptor, eraseAllData))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,23 +16,18 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.cache
|
package org.matrix.android.sdk.internal.session.cache
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.session.cache.CacheService
|
import org.matrix.android.sdk.api.session.cache.CacheService
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultCacheService @Inject constructor(@SessionDatabase
|
internal class DefaultCacheService @Inject constructor(@SessionDatabase
|
||||||
private val clearCacheTask: ClearCacheTask,
|
private val clearCacheTask: ClearCacheTask,
|
||||||
private val taskExecutor: TaskExecutor) : CacheService {
|
private val taskExecutor: TaskExecutor
|
||||||
|
) : CacheService {
|
||||||
|
|
||||||
override fun clearCache(callback: MatrixCallback<Unit>) {
|
override suspend fun clearCache() {
|
||||||
taskExecutor.cancelAll()
|
taskExecutor.cancelAll()
|
||||||
clearCacheTask
|
clearCacheTask.execute(Unit)
|
||||||
.configureWith {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,8 @@ import java.io.File
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class ImageCompressor @Inject constructor() {
|
internal class ImageCompressor @Inject constructor(private val context: Context) {
|
||||||
suspend fun compress(
|
suspend fun compress(
|
||||||
context: Context,
|
|
||||||
imageFile: File,
|
imageFile: File,
|
||||||
desiredWidth: Int,
|
desiredWidth: Int,
|
||||||
desiredHeight: Int,
|
desiredHeight: Int,
|
||||||
|
@ -46,7 +45,7 @@ internal class ImageCompressor @Inject constructor() {
|
||||||
}
|
}
|
||||||
} ?: return@withContext imageFile
|
} ?: return@withContext imageFile
|
||||||
|
|
||||||
val destinationFile = createDestinationFile(context)
|
val destinationFile = createDestinationFile()
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
destinationFile.outputStream().use {
|
destinationFile.outputStream().use {
|
||||||
|
@ -118,7 +117,7 @@ internal class ImageCompressor @Inject constructor() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDestinationFile(context: Context): File {
|
private fun createDestinationFile(): File {
|
||||||
return File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
|
return File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,7 @@ internal object ThumbnailExtractor {
|
||||||
val mediaMetadataRetriever = MediaMetadataRetriever()
|
val mediaMetadataRetriever = MediaMetadataRetriever()
|
||||||
try {
|
try {
|
||||||
mediaMetadataRetriever.setDataSource(context, attachment.queryUri)
|
mediaMetadataRetriever.setDataSource(context, attachment.queryUri)
|
||||||
val thumbnail = mediaMetadataRetriever.frameAtTime
|
mediaMetadataRetriever.frameAtTime?.let { thumbnail ->
|
||||||
|
|
||||||
val outputStream = ByteArrayOutputStream()
|
val outputStream = ByteArrayOutputStream()
|
||||||
thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
|
thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
|
||||||
val thumbnailWidth = thumbnail.width
|
val thumbnailWidth = thumbnail.width
|
||||||
|
@ -63,6 +62,9 @@ internal object ThumbnailExtractor {
|
||||||
)
|
)
|
||||||
thumbnail.recycle()
|
thumbnail.recycle()
|
||||||
outputStream.reset()
|
outputStream.reset()
|
||||||
|
} ?: run {
|
||||||
|
Timber.e("Cannot extract video thumbnail at %s", attachment.queryUri.toString())
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "Cannot extract video thumbnail")
|
Timber.e(e, "Cannot extract video thumbnail")
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -156,7 +156,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
// Do not compress gif
|
// Do not compress gif
|
||||||
&& attachment.mimeType != MimeTypes.Gif
|
&& attachment.mimeType != MimeTypes.Gif
|
||||||
&& params.compressBeforeSending) {
|
&& params.compressBeforeSending) {
|
||||||
fileToUpload = imageCompressor.compress(context, workingFile, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE)
|
fileToUpload = imageCompressor.compress(workingFile, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE)
|
||||||
.also { compressedFile ->
|
.also { compressedFile ->
|
||||||
// Get new Bitmap size
|
// Get new Bitmap size
|
||||||
compressedFile.inputStream().use {
|
compressedFile.inputStream().use {
|
||||||
|
|
|
@ -52,65 +52,60 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
|
||||||
val pepper = identityData.hashLookupPepper
|
val pepper = identityData.hashLookupPepper
|
||||||
val hashDetailResponse = if (pepper == null) {
|
val hashDetailResponse = if (pepper == null) {
|
||||||
// We need to fetch the hash details first
|
// We need to fetch the hash details first
|
||||||
fetchAndStoreHashDetails(identityAPI)
|
fetchHashDetails(identityAPI)
|
||||||
|
.also { identityStore.setHashDetails(it) }
|
||||||
} else {
|
} else {
|
||||||
IdentityHashDetailResponse(pepper, identityData.hashLookupAlgorithm)
|
IdentityHashDetailResponse(pepper, identityData.hashLookupAlgorithm)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hashDetailResponse.algorithms.contains("sha256").not()) {
|
if (hashDetailResponse.algorithms.contains(IdentityHashDetailResponse.ALGORITHM_SHA256).not()) {
|
||||||
// TODO We should ask the user if he is ok to send their 3Pid in clear, but for the moment we do not do it
|
// TODO We should ask the user if he is ok to send their 3Pid in clear, but for the moment we do not do it
|
||||||
// Also, what we have in cache could be outdated, the identity server maybe now supports sha256
|
// Also, what we have in cache could be outdated, the identity server maybe now supports sha256
|
||||||
throw IdentityServiceError.BulkLookupSha256NotSupported
|
throw IdentityServiceError.BulkLookupSha256NotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
val hashedAddresses = withOlmUtility { olmUtility ->
|
val lookUpData = lookUpInternal(identityAPI, params.threePids, hashDetailResponse, true)
|
||||||
params.threePids.map { threePid ->
|
|
||||||
base64ToBase64Url(
|
|
||||||
olmUtility.sha256(threePid.value.toLowerCase(Locale.ROOT)
|
|
||||||
+ " " + threePid.toMedium() + " " + hashDetailResponse.pepper)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val identityLookUpV2Response = lookUpInternal(identityAPI, hashedAddresses, hashDetailResponse, true)
|
|
||||||
|
|
||||||
// Convert back to List<FoundThreePid>
|
// Convert back to List<FoundThreePid>
|
||||||
return handleSuccess(params.threePids, hashedAddresses, identityLookUpV2Response)
|
return handleSuccess(params.threePids, lookUpData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class LookUpData(
|
||||||
|
val hashedAddresses: List<String>,
|
||||||
|
val identityLookUpResponse: IdentityLookUpResponse
|
||||||
|
)
|
||||||
|
|
||||||
private suspend fun lookUpInternal(identityAPI: IdentityAPI,
|
private suspend fun lookUpInternal(identityAPI: IdentityAPI,
|
||||||
hashedAddresses: List<String>,
|
threePids: List<ThreePid>,
|
||||||
hashDetailResponse: IdentityHashDetailResponse,
|
hashDetailResponse: IdentityHashDetailResponse,
|
||||||
canRetry: Boolean): IdentityLookUpResponse {
|
canRetry: Boolean): LookUpData {
|
||||||
|
val hashedAddresses = getHashedAddresses(threePids, hashDetailResponse.pepper)
|
||||||
return try {
|
return try {
|
||||||
|
LookUpData(hashedAddresses,
|
||||||
executeRequest(null) {
|
executeRequest(null) {
|
||||||
apiCall = identityAPI.lookup(IdentityLookUpParams(
|
apiCall = identityAPI.lookup(IdentityLookUpParams(
|
||||||
hashedAddresses,
|
hashedAddresses,
|
||||||
IdentityHashDetailResponse.ALGORITHM_SHA256,
|
IdentityHashDetailResponse.ALGORITHM_SHA256,
|
||||||
hashDetailResponse.pepper
|
hashDetailResponse.pepper
|
||||||
))
|
))
|
||||||
}
|
})
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
// Catch invalid hash pepper and retry
|
// Catch invalid hash pepper and retry
|
||||||
if (canRetry && failure is Failure.ServerError && failure.error.code == MatrixError.M_INVALID_PEPPER) {
|
if (canRetry && failure is Failure.ServerError && failure.error.code == MatrixError.M_INVALID_PEPPER) {
|
||||||
// This is not documented, but the error can contain the new pepper!
|
// This is not documented, but the error can contain the new pepper!
|
||||||
if (!failure.error.newLookupPepper.isNullOrEmpty()) {
|
val newHashDetailResponse = if (!failure.error.newLookupPepper.isNullOrEmpty()) {
|
||||||
// Store it and use it right now
|
// Store it and use it right now
|
||||||
hashDetailResponse.copy(pepper = failure.error.newLookupPepper)
|
hashDetailResponse.copy(pepper = failure.error.newLookupPepper)
|
||||||
.also { identityStore.setHashDetails(it) }
|
|
||||||
.let { lookUpInternal(identityAPI, hashedAddresses, it, false /* Avoid infinite loop */) }
|
|
||||||
} else {
|
} else {
|
||||||
// Retrieve the new hash details
|
// Retrieve the new hash details
|
||||||
val newHashDetailResponse = fetchAndStoreHashDetails(identityAPI)
|
fetchHashDetails(identityAPI)
|
||||||
|
}
|
||||||
if (hashDetailResponse.algorithms.contains(IdentityHashDetailResponse.ALGORITHM_SHA256).not()) {
|
.also { identityStore.setHashDetails(it) }
|
||||||
|
if (newHashDetailResponse.algorithms.contains(IdentityHashDetailResponse.ALGORITHM_SHA256).not()) {
|
||||||
// TODO We should ask the user if he is ok to send their 3Pid in clear, but for the moment we do not do it
|
// TODO We should ask the user if he is ok to send their 3Pid in clear, but for the moment we do not do it
|
||||||
// Also, what we have in cache is maybe outdated, the identity server maybe now support sha256
|
|
||||||
throw IdentityServiceError.BulkLookupSha256NotSupported
|
throw IdentityServiceError.BulkLookupSha256NotSupported
|
||||||
}
|
}
|
||||||
|
lookUpInternal(identityAPI, threePids, newHashDetailResponse, false /* Avoid infinite loop */)
|
||||||
lookUpInternal(identityAPI, hashedAddresses, newHashDetailResponse, false /* Avoid infinite loop */)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Other error
|
// Other error
|
||||||
throw failure
|
throw failure
|
||||||
|
@ -118,16 +113,29 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchAndStoreHashDetails(identityAPI: IdentityAPI): IdentityHashDetailResponse {
|
private fun getHashedAddresses(threePids: List<ThreePid>, pepper: String): List<String> {
|
||||||
return executeRequest<IdentityHashDetailResponse>(null) {
|
return withOlmUtility { olmUtility ->
|
||||||
apiCall = identityAPI.hashDetails()
|
threePids.map { threePid ->
|
||||||
|
base64ToBase64Url(
|
||||||
|
olmUtility.sha256(threePid.value.toLowerCase(Locale.ROOT)
|
||||||
|
+ " " + threePid.toMedium() + " " + pepper)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.also { identityStore.setHashDetails(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSuccess(threePids: List<ThreePid>, hashedAddresses: List<String>, identityLookUpResponse: IdentityLookUpResponse): List<FoundThreePid> {
|
private suspend fun fetchHashDetails(identityAPI: IdentityAPI): IdentityHashDetailResponse {
|
||||||
return identityLookUpResponse.mappings.keys.map { hashedAddress ->
|
return executeRequest(null) {
|
||||||
FoundThreePid(threePids[hashedAddresses.indexOf(hashedAddress)], identityLookUpResponse.mappings[hashedAddress] ?: error(""))
|
apiCall = identityAPI.hashDetails()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSuccess(threePids: List<ThreePid>, lookupData: LookUpData): List<FoundThreePid> {
|
||||||
|
return lookupData.identityLookUpResponse.mappings.keys.map { hashedAddress ->
|
||||||
|
FoundThreePid(
|
||||||
|
threePids[lookupData.hashedAddresses.indexOf(hashedAddress)],
|
||||||
|
lookupData.identityLookUpResponse.mappings[hashedAddress] ?: error("")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,10 @@ package org.matrix.android.sdk.internal.session.media
|
||||||
|
|
||||||
import androidx.collection.LruCache
|
import androidx.collection.LruCache
|
||||||
import org.matrix.android.sdk.api.cache.CacheStrategy
|
import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.media.MediaService
|
import org.matrix.android.sdk.api.session.media.MediaService
|
||||||
import org.matrix.android.sdk.api.session.media.PreviewUrlData
|
import org.matrix.android.sdk.api.session.media.PreviewUrlData
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.getLatestEventId
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.internal.util.getOrPut
|
import org.matrix.android.sdk.internal.util.getOrPut
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -34,11 +35,12 @@ internal class DefaultMediaService @Inject constructor(
|
||||||
// Cache of extracted URLs
|
// Cache of extracted URLs
|
||||||
private val extractedUrlsCache = LruCache<String, List<String>>(1_000)
|
private val extractedUrlsCache = LruCache<String, List<String>>(1_000)
|
||||||
|
|
||||||
override fun extractUrls(event: Event): List<String> {
|
override fun extractUrls(event: TimelineEvent): List<String> {
|
||||||
return extractedUrlsCache.getOrPut(event.cacheKey()) { urlsExtractor.extract(event) }
|
return extractedUrlsCache.getOrPut(event.cacheKey()) { urlsExtractor.extract(event) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Event.cacheKey() = "${eventId ?: ""}-${roomId ?: ""}"
|
// Use the id of the latest Event edition
|
||||||
|
private fun TimelineEvent.cacheKey() = "${getLatestEventId()}-${root.roomId ?: ""}"
|
||||||
|
|
||||||
override suspend fun getRawPreviewUrl(url: String, timestamp: Long?): JsonDict {
|
override suspend fun getRawPreviewUrl(url: String, timestamp: Long?): JsonDict {
|
||||||
return getRawPreviewUrlTask.execute(GetRawPreviewUrlTask.Params(url, timestamp))
|
return getRawPreviewUrlTask.execute(GetRawPreviewUrlTask.Params(url, timestamp))
|
||||||
|
|
|
@ -17,21 +17,19 @@
|
||||||
package org.matrix.android.sdk.internal.session.media
|
package org.matrix.android.sdk.internal.session.media
|
||||||
|
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
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.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class UrlsExtractor @Inject constructor() {
|
internal class UrlsExtractor @Inject constructor() {
|
||||||
// Sadly Patterns.WEB_URL_WITH_PROTOCOL is not public so filter the protocol later
|
// Sadly Patterns.WEB_URL_WITH_PROTOCOL is not public so filter the protocol later
|
||||||
private val urlRegex = Patterns.WEB_URL.toRegex()
|
private val urlRegex = Patterns.WEB_URL.toRegex()
|
||||||
|
|
||||||
fun extract(event: Event): List<String> {
|
fun extract(event: TimelineEvent): List<String> {
|
||||||
return event.takeIf { it.getClearType() == EventType.MESSAGE }
|
return event.takeIf { it.root.getClearType() == EventType.MESSAGE }
|
||||||
?.getClearContent()
|
?.getLastMessageContent()
|
||||||
?.toModel<MessageContent>()
|
|
||||||
?.takeIf {
|
?.takeIf {
|
||||||
it.msgType == MessageType.MSGTYPE_TEXT
|
it.msgType == MessageType.MSGTYPE_TEXT
|
||||||
|| it.msgType == MessageType.MSGTYPE_NOTICE
|
|| it.msgType == MessageType.MSGTYPE_NOTICE
|
||||||
|
|
|
@ -22,6 +22,7 @@ import androidx.lifecycle.LiveData
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
import org.matrix.android.sdk.api.session.profile.ProfileService
|
import org.matrix.android.sdk.api.session.profile.ProfileService
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
@ -170,14 +171,12 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun finalizeAddingThreePid(threePid: ThreePid,
|
override fun finalizeAddingThreePid(threePid: ThreePid,
|
||||||
uiaSession: String?,
|
userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
|
||||||
accountPassword: String?,
|
|
||||||
matrixCallback: MatrixCallback<Unit>): Cancelable {
|
matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||||
return finalizeAddingThreePidTask
|
return finalizeAddingThreePidTask
|
||||||
.configureWith(FinalizeAddingThreePidTask.Params(
|
.configureWith(FinalizeAddingThreePidTask.Params(
|
||||||
threePid = threePid,
|
threePid = threePid,
|
||||||
session = uiaSession,
|
userInteractiveAuthInterceptor = userInteractiveAuthInterceptor,
|
||||||
accountPassword = accountPassword,
|
|
||||||
userWantsToCancel = false
|
userWantsToCancel = false
|
||||||
)) {
|
)) {
|
||||||
callback = alsoRefresh(matrixCallback)
|
callback = alsoRefresh(matrixCallback)
|
||||||
|
@ -189,8 +188,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
||||||
return finalizeAddingThreePidTask
|
return finalizeAddingThreePidTask
|
||||||
.configureWith(FinalizeAddingThreePidTask.Params(
|
.configureWith(FinalizeAddingThreePidTask.Params(
|
||||||
threePid = threePid,
|
threePid = threePid,
|
||||||
session = null,
|
userInteractiveAuthInterceptor = null,
|
||||||
accountPassword = null,
|
|
||||||
userWantsToCancel = true
|
userWantsToCancel = true
|
||||||
)) {
|
)) {
|
||||||
callback = alsoRefresh(matrixCallback)
|
callback = alsoRefresh(matrixCallback)
|
||||||
|
|
|
@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.profile
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class FinalizeAddThreePidBody(
|
internal data class FinalizeAddThreePidBody(
|
||||||
|
@ -37,5 +36,5 @@ internal data class FinalizeAddThreePidBody(
|
||||||
* Additional authentication information for the user-interactive authentication API.
|
* Additional authentication information for the user-interactive authentication API.
|
||||||
*/
|
*/
|
||||||
@Json(name = "auth")
|
@Json(name = "auth")
|
||||||
val auth: UserPasswordAuth?
|
val auth: Map<String, *>? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,10 +17,12 @@
|
||||||
package org.matrix.android.sdk.internal.session.profile
|
package org.matrix.android.sdk.internal.session.profile
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
|
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
import org.matrix.android.sdk.internal.auth.registration.handleUIA
|
||||||
|
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
|
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
@ -29,13 +31,14 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal abstract class FinalizeAddingThreePidTask : Task<FinalizeAddingThreePidTask.Params, Unit> {
|
internal abstract class FinalizeAddingThreePidTask : Task<FinalizeAddingThreePidTask.Params, Unit> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val threePid: ThreePid,
|
val threePid: ThreePid,
|
||||||
val session: String?,
|
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor?,
|
||||||
val accountPassword: String?,
|
val userAuthParam: UIABaseAuth? = null,
|
||||||
val userWantsToCancel: Boolean
|
val userWantsToCancel: Boolean
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -62,22 +65,23 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
|
||||||
val body = FinalizeAddThreePidBody(
|
val body = FinalizeAddThreePidBody(
|
||||||
clientSecret = pendingThreePids.clientSecret,
|
clientSecret = pendingThreePids.clientSecret,
|
||||||
sid = pendingThreePids.sid,
|
sid = pendingThreePids.sid,
|
||||||
auth = if (params.session != null && params.accountPassword != null) {
|
auth = params.userAuthParam?.asMap()
|
||||||
UserPasswordAuth(
|
|
||||||
session = params.session,
|
|
||||||
user = userId,
|
|
||||||
password = params.accountPassword
|
|
||||||
)
|
|
||||||
} else null
|
|
||||||
)
|
)
|
||||||
apiCall = profileAPI.finalizeAddThreePid(body)
|
apiCall = profileAPI.finalizeAddThreePid(body)
|
||||||
}
|
}
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
|
if (params.userInteractiveAuthInterceptor == null
|
||||||
|
|| !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
|
||||||
|
execute(params.copy(userAuthParam = auth))
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Timber.d("## UIA: propagate failure")
|
||||||
throw throwable.toRegistrationFlowResponse()
|
throw throwable.toRegistrationFlowResponse()
|
||||||
?.let { Failure.RegistrationFlowError(it) }
|
?.let { Failure.RegistrationFlowError(it) }
|
||||||
?: throwable
|
?: throwable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cleanupDatabase(params)
|
cleanupDatabase(params)
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,9 +143,11 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun canEnableEncryption(params: CreateRoomParams): Boolean {
|
private suspend fun canEnableEncryption(params: CreateRoomParams): Boolean {
|
||||||
return (params.enableEncryptionIfInvitedUsersSupportIt
|
return params.enableEncryptionIfInvitedUsersSupportIt
|
||||||
&& crossSigningService.isCrossSigningVerified()
|
// Parity with web, enable if users have encryption ready devices
|
||||||
&& params.invite3pids.isEmpty())
|
// for now remove checks on cross signing and 3pid invites
|
||||||
|
// && crossSigningService.isCrossSigningVerified()
|
||||||
|
&& params.invite3pids.isEmpty()
|
||||||
&& params.invitedUserIds.isNotEmpty()
|
&& params.invitedUserIds.isNotEmpty()
|
||||||
&& params.invitedUserIds.let { userIds ->
|
&& params.invitedUserIds.let { userIds ->
|
||||||
val keys = deviceListManager.downloadKeys(userIds, forceDownload = false)
|
val keys = deviceListManager.downloadKeys(userIds, forceDownload = false)
|
||||||
|
|
|
@ -141,7 +141,7 @@ internal class DefaultRelationService @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
|
override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
|
||||||
val params = FetchEditHistoryTask.Params(roomId, cryptoSessionInfoProvider.isRoomEncrypted(roomId), eventId)
|
val params = FetchEditHistoryTask.Params(roomId, eventId)
|
||||||
fetchEditHistoryTask
|
fetchEditHistoryTask
|
||||||
.configureWith(params) {
|
.configureWith(params) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.relation
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
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.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
|
import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
|
@ -25,25 +26,27 @@ import org.matrix.android.sdk.internal.task.Task
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface FetchEditHistoryTask : Task<FetchEditHistoryTask.Params, List<Event>> {
|
internal interface FetchEditHistoryTask : Task<FetchEditHistoryTask.Params, List<Event>> {
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val isRoomEncrypted: Boolean,
|
|
||||||
val eventId: String
|
val eventId: String
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultFetchEditHistoryTask @Inject constructor(
|
internal class DefaultFetchEditHistoryTask @Inject constructor(
|
||||||
private val roomAPI: RoomAPI,
|
private val roomAPI: RoomAPI,
|
||||||
private val globalErrorReceiver: GlobalErrorReceiver
|
private val globalErrorReceiver: GlobalErrorReceiver,
|
||||||
|
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider
|
||||||
) : FetchEditHistoryTask {
|
) : FetchEditHistoryTask {
|
||||||
|
|
||||||
override suspend fun execute(params: FetchEditHistoryTask.Params): List<Event> {
|
override suspend fun execute(params: FetchEditHistoryTask.Params): List<Event> {
|
||||||
|
val isRoomEncrypted = cryptoSessionInfoProvider.isRoomEncrypted(params.roomId)
|
||||||
val response = executeRequest<RelationsResponse>(globalErrorReceiver) {
|
val response = executeRequest<RelationsResponse>(globalErrorReceiver) {
|
||||||
apiCall = roomAPI.getRelations(params.roomId,
|
apiCall = roomAPI.getRelations(
|
||||||
params.eventId,
|
roomId = params.roomId,
|
||||||
RelationType.REPLACE,
|
eventId = params.eventId,
|
||||||
if (params.isRoomEncrypted) EventType.ENCRYPTED else EventType.MESSAGE)
|
relationType = RelationType.REPLACE,
|
||||||
|
eventType = if (isRoomEncrypted) EventType.ENCRYPTED else EventType.MESSAGE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val events = response.chunks.toMutableList()
|
val events = response.chunks.toMutableList()
|
||||||
|
|
|
@ -140,14 +140,13 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
.queryActiveRoomMembersEvent()
|
.queryActiveRoomMembersEvent()
|
||||||
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
||||||
.findAll()
|
.findAll()
|
||||||
.asSequence()
|
|
||||||
.map { it.userId }
|
.map { it.userId }
|
||||||
|
|
||||||
roomSummaryEntity.otherMemberIds.clear()
|
roomSummaryEntity.otherMemberIds.clear()
|
||||||
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
|
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
|
||||||
if (roomSummaryEntity.isEncrypted) {
|
if (roomSummaryEntity.isEncrypted) {
|
||||||
// mmm maybe we could only refresh shield instead of checking trust also?
|
// mmm maybe we could only refresh shield instead of checking trust also?
|
||||||
crossSigningService.onUsersDeviceUpdate(roomSummaryEntity.otherMemberIds.toList())
|
crossSigningService.onUsersDeviceUpdate(otherRoomMembers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,16 +21,34 @@ import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class EventContextResponse(
|
internal data class EventContextResponse(
|
||||||
|
/**
|
||||||
|
* Details of the requested event.
|
||||||
|
*/
|
||||||
@Json(name = "event") val event: Event,
|
@Json(name = "event") val event: Event,
|
||||||
|
/**
|
||||||
|
* A token that can be used to paginate backwards with.
|
||||||
|
*/
|
||||||
@Json(name = "start") override val start: String? = null,
|
@Json(name = "start") override val start: String? = null,
|
||||||
@Json(name = "events_before") val eventsBefore: List<Event> = emptyList(),
|
/**
|
||||||
@Json(name = "events_after") val eventsAfter: List<Event> = emptyList(),
|
* A list of room events that happened just before the requested event, in reverse-chronological order.
|
||||||
|
*/
|
||||||
|
@Json(name = "events_before") val eventsBefore: List<Event>? = null,
|
||||||
|
/**
|
||||||
|
* A list of room events that happened just after the requested event, in chronological order.
|
||||||
|
*/
|
||||||
|
@Json(name = "events_after") val eventsAfter: List<Event>? = null,
|
||||||
|
/**
|
||||||
|
* A token that can be used to paginate forwards with.
|
||||||
|
*/
|
||||||
@Json(name = "end") override val end: String? = null,
|
@Json(name = "end") override val end: String? = null,
|
||||||
@Json(name = "state") override val stateEvents: List<Event> = emptyList()
|
/**
|
||||||
|
* The state of the room at the last event returned.
|
||||||
|
*/
|
||||||
|
@Json(name = "state") override val stateEvents: List<Event>? = null
|
||||||
) : TokenChunkEvent {
|
) : TokenChunkEvent {
|
||||||
|
|
||||||
override val events: List<Event> by lazy {
|
override val events: List<Event> by lazy {
|
||||||
eventsAfter.reversed() + listOf(event) + eventsBefore
|
eventsAfter.orEmpty().reversed() + event + eventsBefore.orEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,28 @@ import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class PaginationResponse(
|
internal data class PaginationResponse(
|
||||||
|
/**
|
||||||
|
* The token the pagination starts from. If dir=b this will be the token supplied in from.
|
||||||
|
*/
|
||||||
@Json(name = "start") override val start: String? = null,
|
@Json(name = "start") override val start: String? = null,
|
||||||
|
/**
|
||||||
|
* The token the pagination ends at. If dir=b this token should be used again to request even earlier events.
|
||||||
|
*/
|
||||||
@Json(name = "end") override val end: String? = null,
|
@Json(name = "end") override val end: String? = null,
|
||||||
@Json(name = "chunk") override val events: List<Event> = emptyList(),
|
/**
|
||||||
@Json(name = "state") override val stateEvents: List<Event> = emptyList()
|
* A list of room events. The order depends on the dir parameter. For dir=b events will be in
|
||||||
) : TokenChunkEvent
|
* reverse-chronological order, for dir=f in chronological order, so that events start at the from point.
|
||||||
|
*/
|
||||||
|
@Json(name = "chunk") val chunk: List<Event>? = null,
|
||||||
|
/**
|
||||||
|
* A list of state events relevant to showing the chunk. For example, if lazy_load_members is enabled
|
||||||
|
* in the filter then this may contain the membership events for the senders of events in the chunk.
|
||||||
|
*
|
||||||
|
* Unless include_redundant_members is true, the server may remove membership events which would have
|
||||||
|
* already been sent to the client in prior calls to this endpoint, assuming the membership of those members has not changed.
|
||||||
|
*/
|
||||||
|
@Json(name = "state") override val stateEvents: List<Event>? = null
|
||||||
|
) : TokenChunkEvent {
|
||||||
|
override val events: List<Event>
|
||||||
|
get() = chunk.orEmpty()
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ internal interface TokenChunkEvent {
|
||||||
val start: String?
|
val start: String?
|
||||||
val end: String?
|
val end: String?
|
||||||
val events: List<Event>
|
val events: List<Event>
|
||||||
val stateEvents: List<Event>
|
val stateEvents: List<Event>?
|
||||||
|
|
||||||
fun hasMore() = start != end
|
fun hasMore() = start != end
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,7 +156,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return if (receivedChunk.events.isEmpty()) {
|
return if (receivedChunk.events.isEmpty()) {
|
||||||
if (receivedChunk.start != receivedChunk.end) {
|
if (receivedChunk.hasMore()) {
|
||||||
Result.SHOULD_FETCH_MORE
|
Result.SHOULD_FETCH_MORE
|
||||||
} else {
|
} else {
|
||||||
Result.REACHED_END
|
Result.REACHED_END
|
||||||
|
@ -196,7 +196,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
|
||||||
|
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
for (stateEvent in stateEvents) {
|
stateEvents?.forEach { stateEvent ->
|
||||||
val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it }
|
val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it }
|
||||||
val stateEventEntity = stateEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
|
val stateEventEntity = stateEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
|
||||||
currentChunk.addStateEvent(roomId, stateEventEntity, direction)
|
currentChunk.addStateEvent(roomId, stateEventEntity, direction)
|
||||||
|
@ -205,9 +205,9 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val eventIds = ArrayList<String>(eventList.size)
|
val eventIds = ArrayList<String>(eventList.size)
|
||||||
for (event in eventList) {
|
eventList.forEach { event ->
|
||||||
if (event.eventId == null || event.senderId == null) {
|
if (event.eventId == null || event.senderId == null) {
|
||||||
continue
|
return@forEach
|
||||||
}
|
}
|
||||||
val ageLocalTs = event.unsignedData?.age?.let { now - it }
|
val ageLocalTs = event.unsignedData?.age?.let { now - it }
|
||||||
eventIds.add(event.eventId)
|
eventIds.add(event.eventId)
|
||||||
|
|
|
@ -56,8 +56,8 @@ internal class DefaultGetUploadsTask @Inject constructor(
|
||||||
private val roomAPI: RoomAPI,
|
private val roomAPI: RoomAPI,
|
||||||
private val tokenStore: SyncTokenStore,
|
private val tokenStore: SyncTokenStore,
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
private val globalErrorReceiver: GlobalErrorReceiver)
|
private val globalErrorReceiver: GlobalErrorReceiver
|
||||||
: GetUploadsTask {
|
) : GetUploadsTask {
|
||||||
|
|
||||||
override suspend fun execute(params: GetUploadsTask.Params): GetUploadsResult {
|
override suspend fun execute(params: GetUploadsTask.Params): GetUploadsResult {
|
||||||
val result: GetUploadsResult
|
val result: GetUploadsResult
|
||||||
|
|
|
@ -16,45 +16,25 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.signout
|
package org.matrix.android.sdk.internal.session.signout
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||||
import org.matrix.android.sdk.api.session.signout.SignOutService
|
import org.matrix.android.sdk.api.session.signout.SignOutService
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import org.matrix.android.sdk.internal.auth.SessionParamsStore
|
import org.matrix.android.sdk.internal.auth.SessionParamsStore
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
|
||||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
|
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
|
||||||
private val signInAgainTask: SignInAgainTask,
|
private val signInAgainTask: SignInAgainTask,
|
||||||
private val sessionParamsStore: SessionParamsStore,
|
private val sessionParamsStore: SessionParamsStore
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
) : SignOutService {
|
||||||
private val taskExecutor: TaskExecutor) : SignOutService {
|
|
||||||
|
|
||||||
override fun signInAgain(password: String,
|
override suspend fun signInAgain(password: String) {
|
||||||
callback: MatrixCallback<Unit>): Cancelable {
|
signInAgainTask.execute(SignInAgainTask.Params(password))
|
||||||
return signInAgainTask
|
|
||||||
.configureWith(SignInAgainTask.Params(password)) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateCredentials(credentials: Credentials,
|
override suspend fun updateCredentials(credentials: Credentials) {
|
||||||
callback: MatrixCallback<Unit>): Cancelable {
|
|
||||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
|
||||||
sessionParamsStore.updateCredentials(credentials)
|
sessionParamsStore.updateCredentials(credentials)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun signOut(signOutFromHomeserver: Boolean,
|
override suspend fun signOut(signOutFromHomeserver: Boolean) {
|
||||||
callback: MatrixCallback<Unit>): Cancelable {
|
return signOutTask.execute(SignOutTask.Params(signOutFromHomeserver))
|
||||||
return signOutTask
|
|
||||||
.configureWith(SignOutTask.Params(signOutFromHomeserver)) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ internal class WidgetFactory @Inject constructor(private val userDataSource: Use
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val isAddedByMe = widgetEvent.senderId == userId
|
val isAddedByMe = widgetEvent.senderId == userId
|
||||||
val computedUrl = widgetContent.computeURL(widgetEvent.roomId)
|
val computedUrl = widgetContent.computeURL(widgetEvent.roomId, widgetId)
|
||||||
return Widget(
|
return Widget(
|
||||||
widgetContent = widgetContent,
|
widgetContent = widgetContent,
|
||||||
event = widgetEvent,
|
event = widgetEvent,
|
||||||
|
@ -65,13 +65,14 @@ internal class WidgetFactory @Inject constructor(private val userDataSource: Use
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun WidgetContent.computeURL(roomId: String?): String? {
|
private fun WidgetContent.computeURL(roomId: String?, widgetId: String): String? {
|
||||||
var computedUrl = url ?: return null
|
var computedUrl = url ?: return null
|
||||||
val myUser = userDataSource.getUser(userId)
|
val myUser = userDataSource.getUser(userId)
|
||||||
computedUrl = computedUrl
|
computedUrl = computedUrl
|
||||||
.replace("\$matrix_user_id", userId)
|
.replace("\$matrix_user_id", userId)
|
||||||
.replace("\$matrix_display_name", myUser?.displayName ?: userId)
|
.replace("\$matrix_display_name", myUser?.displayName ?: userId)
|
||||||
.replace("\$matrix_avatar_url", myUser?.avatarUrl ?: "")
|
.replace("\$matrix_avatar_url", myUser?.avatarUrl ?: "")
|
||||||
|
.replace("\$matrix_widget_id", widgetId)
|
||||||
|
|
||||||
if (roomId != null) {
|
if (roomId != null) {
|
||||||
computedUrl = computedUrl.replace("\$matrix_room_id", roomId)
|
computedUrl = computedUrl.replace("\$matrix_room_id", roomId)
|
||||||
|
|
|
@ -19,11 +19,11 @@ apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-parcelize'
|
apply plugin: 'kotlin-parcelize'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 19
|
minSdkVersion 19
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ class AudioPicker : Picker<MultiPickerAudioType>() {
|
||||||
context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd ->
|
context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd ->
|
||||||
val mediaMetadataRetriever = MediaMetadataRetriever()
|
val mediaMetadataRetriever = MediaMetadataRetriever()
|
||||||
mediaMetadataRetriever.setDataSource(pfd.fileDescriptor)
|
mediaMetadataRetriever.setDataSource(pfd.fileDescriptor)
|
||||||
duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
|
duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
audioList.add(
|
audioList.add(
|
||||||
|
|
|
@ -61,10 +61,10 @@ class VideoPicker : Picker<MultiPickerVideoType>() {
|
||||||
context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd ->
|
context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd ->
|
||||||
val mediaMetadataRetriever = MediaMetadataRetriever()
|
val mediaMetadataRetriever = MediaMetadataRetriever()
|
||||||
mediaMetadataRetriever.setDataSource(pfd.fileDescriptor)
|
mediaMetadataRetriever.setDataSource(pfd.fileDescriptor)
|
||||||
duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong()
|
duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L
|
||||||
width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH).toInt()
|
width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toInt() ?: 0
|
||||||
height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT).toInt()
|
height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toInt() ?: 0
|
||||||
orientation = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION).toInt()
|
orientation = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)?.toInt() ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
videoList.add(
|
videoList.add(
|
||||||
|
|
|
@ -13,7 +13,7 @@ kapt {
|
||||||
// Note: 2 digits max for each value
|
// Note: 2 digits max for each value
|
||||||
ext.versionMajor = 1
|
ext.versionMajor = 1
|
||||||
ext.versionMinor = 0
|
ext.versionMinor = 0
|
||||||
ext.versionPatch = 15
|
ext.versionPatch = 16
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
def cmd = 'git show -s --format=%ct'
|
def cmd = 'git show -s --format=%ct'
|
||||||
|
@ -101,7 +101,7 @@ ext.abiVersionCodes = ["armeabi-v7a": 1, "arm64-v8a": 2, "x86": 3, "x86_64": 4].
|
||||||
def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0
|
def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
|
|
||||||
// Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use
|
// Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use
|
||||||
// Ref: https://issuetracker.google.com/issues/144111441
|
// Ref: https://issuetracker.google.com/issues/144111441
|
||||||
|
@ -111,7 +111,7 @@ android {
|
||||||
applicationId "im.vector.app"
|
applicationId "im.vector.app"
|
||||||
// Set to API 21: see #405
|
// Set to API 21: see #405
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
// `develop` branch will have version code from timestamp, to ensure each build from CI has a incremented versionCode.
|
// `develop` branch will have version code from timestamp, to ensure each build from CI has a incremented versionCode.
|
||||||
|
|
|
@ -42,13 +42,18 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.matrix.android.sdk.api.Matrix
|
import org.matrix.android.sdk.api.Matrix
|
||||||
|
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||||
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
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.VerificationMethod
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
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.VerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||||
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
|
@ -67,10 +72,18 @@ class VerifySessionInteractiveTest : VerificationTestBase() {
|
||||||
existingSession = createAccountAndSync(matrix, userName, password, true)
|
existingSession = createAccountAndSync(matrix, userName, password, true)
|
||||||
doSync<Unit> {
|
doSync<Unit> {
|
||||||
existingSession!!.cryptoService().crossSigningService()
|
existingSession!!.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(UserPasswordAuth(
|
.initializeCrossSigning(
|
||||||
|
object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(
|
||||||
|
UserPasswordAuth(
|
||||||
user = existingSession!!.myUserId,
|
user = existingSession!!.myUserId,
|
||||||
password = "password"
|
password = "password",
|
||||||
), it)
|
session = flowResponse.session
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,13 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.matrix.android.sdk.api.Matrix
|
import org.matrix.android.sdk.api.Matrix
|
||||||
|
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.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
import kotlin.coroutines.Continuation
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
|
@ -67,17 +72,35 @@ class VerifySessionPassphraseTest : VerificationTestBase() {
|
||||||
existingSession = createAccountAndSync(matrix, userName, password, true)
|
existingSession = createAccountAndSync(matrix, userName, password, true)
|
||||||
doSync<Unit> {
|
doSync<Unit> {
|
||||||
existingSession!!.cryptoService().crossSigningService()
|
existingSession!!.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(UserPasswordAuth(
|
.initializeCrossSigning(
|
||||||
|
object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(
|
||||||
|
UserPasswordAuth(
|
||||||
user = existingSession!!.myUserId,
|
user = existingSession!!.myUserId,
|
||||||
password = "password"
|
password = "password",
|
||||||
), it)
|
session = flowResponse.session
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val task = BootstrapCrossSigningTask(existingSession!!, StringProvider(context.resources))
|
val task = BootstrapCrossSigningTask(existingSession!!, StringProvider(context.resources))
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
task.execute(Params(
|
task.execute(Params(
|
||||||
userPasswordAuth = UserPasswordAuth(password = password),
|
userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor {
|
||||||
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
|
promise.resume(
|
||||||
|
UserPasswordAuth(
|
||||||
|
user = existingSession!!.myUserId,
|
||||||
|
password = password,
|
||||||
|
session = flowResponse.session
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
passphrase = passphrase,
|
passphrase = passphrase,
|
||||||
setupMode = SetupMode.NORMAL
|
setupMode = SetupMode.NORMAL
|
||||||
))
|
))
|
||||||
|
|
|
@ -196,6 +196,8 @@ class UiAllScreensSanityTest {
|
||||||
pressBack()
|
pressBack()
|
||||||
clickMenu(R.id.video_call)
|
clickMenu(R.id.video_call)
|
||||||
pressBack()
|
pressBack()
|
||||||
|
clickMenu(R.id.search)
|
||||||
|
pressBack()
|
||||||
|
|
||||||
pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,6 @@
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".features.MainActivity"
|
android:name=".features.MainActivity"
|
||||||
android:taskAffinity=""
|
|
||||||
android:theme="@style/AppTheme.Launcher" />
|
android:theme="@style/AppTheme.Launcher" />
|
||||||
|
|
||||||
<!-- Activity alias for the launcher Activity (must be declared after the Activity it targets) -->
|
<!-- Activity alias for the launcher Activity (must be declared after the Activity it targets) -->
|
||||||
|
@ -242,6 +241,27 @@
|
||||||
<activity android:name=".features.home.room.detail.search.SearchActivity" />
|
<activity android:name=".features.home.room.detail.search.SearchActivity" />
|
||||||
<activity android:name=".features.usercode.UserCodeActivity" />
|
<activity android:name=".features.usercode.UserCodeActivity" />
|
||||||
|
|
||||||
|
<!-- Single instance is very important for the custom scheme callback-->
|
||||||
|
<activity android:name=".features.auth.ReAuthActivity"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
android:exported="false">
|
||||||
|
|
||||||
|
<!-- XXX: UIA SSO has only web fallback, i.e no url redirect, so for now we comment this out
|
||||||
|
hopefully, we would use it when finally available
|
||||||
|
-->
|
||||||
|
<!-- Add intent filter to handle redirection URL after SSO login in external browser -->
|
||||||
|
<!-- <intent-filter>-->
|
||||||
|
<!-- <action android:name="android.intent.action.VIEW" />-->
|
||||||
|
|
||||||
|
<!-- <category android:name="android.intent.category.DEFAULT" />-->
|
||||||
|
<!-- <category android:name="android.intent.category.BROWSABLE" />-->
|
||||||
|
|
||||||
|
<!-- <data-->
|
||||||
|
<!-- android:host="reauth"-->
|
||||||
|
<!-- android:scheme="element" />-->
|
||||||
|
<!-- </intent-filter>-->
|
||||||
|
</activity>
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -28,7 +28,7 @@ import im.vector.app.features.crypto.keysbackup.settings.KeysBackupSettingsFragm
|
||||||
import im.vector.app.features.crypto.quads.SharedSecuredStorageKeyFragment
|
import im.vector.app.features.crypto.quads.SharedSecuredStorageKeyFragment
|
||||||
import im.vector.app.features.crypto.quads.SharedSecuredStoragePassphraseFragment
|
import im.vector.app.features.crypto.quads.SharedSecuredStoragePassphraseFragment
|
||||||
import im.vector.app.features.crypto.quads.SharedSecuredStorageResetAllFragment
|
import im.vector.app.features.crypto.quads.SharedSecuredStorageResetAllFragment
|
||||||
import im.vector.app.features.crypto.recover.BootstrapAccountPasswordFragment
|
import im.vector.app.features.crypto.recover.BootstrapReAuthFragment
|
||||||
import im.vector.app.features.crypto.recover.BootstrapConclusionFragment
|
import im.vector.app.features.crypto.recover.BootstrapConclusionFragment
|
||||||
import im.vector.app.features.crypto.recover.BootstrapConfirmPassphraseFragment
|
import im.vector.app.features.crypto.recover.BootstrapConfirmPassphraseFragment
|
||||||
import im.vector.app.features.crypto.recover.BootstrapEnterPassphraseFragment
|
import im.vector.app.features.crypto.recover.BootstrapEnterPassphraseFragment
|
||||||
|
@ -522,8 +522,8 @@ interface FragmentModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(BootstrapAccountPasswordFragment::class)
|
@FragmentKey(BootstrapReAuthFragment::class)
|
||||||
fun bindBootstrapAccountPasswordFragment(fragment: BootstrapAccountPasswordFragment): Fragment
|
fun bindBootstrapReAuthFragment(fragment: BootstrapReAuthFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
|
|
|
@ -25,6 +25,7 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
import im.vector.app.core.preference.UserAvatarPreference
|
import im.vector.app.core.preference.UserAvatarPreference
|
||||||
import im.vector.app.features.MainActivity
|
import im.vector.app.features.MainActivity
|
||||||
|
import im.vector.app.features.auth.ReAuthActivity
|
||||||
import im.vector.app.features.call.CallControlsBottomSheet
|
import im.vector.app.features.call.CallControlsBottomSheet
|
||||||
import im.vector.app.features.call.VectorCallActivity
|
import im.vector.app.features.call.VectorCallActivity
|
||||||
import im.vector.app.features.call.conference.VectorJitsiActivity
|
import im.vector.app.features.call.conference.VectorJitsiActivity
|
||||||
|
@ -145,6 +146,7 @@ interface ScreenComponent {
|
||||||
fun inject(activity: VectorJitsiActivity)
|
fun inject(activity: VectorJitsiActivity)
|
||||||
fun inject(activity: SearchActivity)
|
fun inject(activity: SearchActivity)
|
||||||
fun inject(activity: UserCodeActivity)
|
fun inject(activity: UserCodeActivity)
|
||||||
|
fun inject(activity: ReAuthActivity)
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* BottomSheets
|
* BottomSheets
|
||||||
|
|
|
@ -105,11 +105,13 @@ class DefaultErrorFormatter @Inject constructor(
|
||||||
HttpURLConnection.HTTP_NOT_FOUND ->
|
HttpURLConnection.HTTP_NOT_FOUND ->
|
||||||
// homeserver not found
|
// homeserver not found
|
||||||
stringProvider.getString(R.string.login_error_no_homeserver_found)
|
stringProvider.getString(R.string.login_error_no_homeserver_found)
|
||||||
|
HttpURLConnection.HTTP_UNAUTHORIZED ->
|
||||||
|
// uia errors?
|
||||||
|
stringProvider.getString(R.string.error_unauthorized)
|
||||||
else ->
|
else ->
|
||||||
throwable.localizedMessage
|
throwable.localizedMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is SsoFlowNotSupportedYet -> stringProvider.getString(R.string.error_sso_flow_not_supported_yet)
|
|
||||||
else -> throwable.localizedMessage
|
else -> throwable.localizedMessage
|
||||||
}
|
}
|
||||||
?: stringProvider.getString(R.string.unknown_error)
|
?: stringProvider.getString(R.string.unknown_error)
|
||||||
|
|
|
@ -19,10 +19,12 @@ package im.vector.app.core.platform
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.WindowInsetsController
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
|
@ -33,6 +35,7 @@ import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentFactory
|
import androidx.fragment.app.FragmentFactory
|
||||||
|
@ -410,7 +413,18 @@ abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScr
|
||||||
/**
|
/**
|
||||||
* Force to render the activity in fullscreen
|
* Force to render the activity in fullscreen
|
||||||
*/
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
private fun setFullScreen() {
|
private fun setFullScreen() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
// New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
window.setDecorFitsSystemWindows(false)
|
||||||
|
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE
|
||||||
|
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
||||||
|
// New API instead of FLAG_TRANSLUCENT_STATUS
|
||||||
|
window.statusBarColor = ContextCompat.getColor(this, im.vector.lib.attachmentviewer.R.color.half_transparent_status_bar)
|
||||||
|
// New API instead of FLAG_TRANSLUCENT_NAVIGATION
|
||||||
|
window.navigationBarColor = ContextCompat.getColor(this, im.vector.lib.attachmentviewer.R.color.half_transparent_status_bar)
|
||||||
|
} else {
|
||||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
@ -418,6 +432,7 @@ abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScr
|
||||||
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||||
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
|
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* MENU MANAGEMENT
|
* MENU MANAGEMENT
|
||||||
|
|
|
@ -200,6 +200,7 @@ abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScre
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun showLoadingDialog(message: CharSequence? = null, cancelable: Boolean = false) {
|
protected fun showLoadingDialog(message: CharSequence? = null, cancelable: Boolean = false) {
|
||||||
|
progress?.dismiss()
|
||||||
progress = ProgressDialog(requireContext()).apply {
|
progress = ProgressDialog(requireContext()).apply {
|
||||||
setCancelable(cancelable)
|
setCancelable(cancelable)
|
||||||
setMessage(message ?: getString(R.string.please_wait))
|
setMessage(message ?: getString(R.string.please_wait))
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.app.core.ui.list
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic button list item.
|
||||||
|
*/
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_positive_button)
|
||||||
|
abstract class GenericPositiveButtonItem : VectorEpoxyModel<GenericPositiveButtonItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var text: String? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var buttonClickAction: View.OnClickListener? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
@ColorInt
|
||||||
|
var textColor: Int? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
@DrawableRes
|
||||||
|
var iconRes: Int? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
holder.button.text = text
|
||||||
|
if (iconRes != null) {
|
||||||
|
holder.button.setIconResource(iconRes!!)
|
||||||
|
} else {
|
||||||
|
holder.button.icon = null
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonClickAction?.let { holder.button.setOnClickListener(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val button by bind<MaterialButton>(R.id.itemGenericItemButton)
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
@ -45,10 +46,8 @@ import im.vector.app.features.signout.soft.SoftLogoutActivity
|
||||||
import im.vector.app.features.ui.UiStateRepository
|
import im.vector.app.features.ui.UiStateRepository
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.failure.GlobalError
|
import org.matrix.android.sdk.api.failure.GlobalError
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -147,38 +146,39 @@ class MainActivity : VectorBaseActivity<FragmentLoadingBinding>(), UnlockedActiv
|
||||||
}
|
}
|
||||||
when {
|
when {
|
||||||
args.isAccountDeactivated -> {
|
args.isAccountDeactivated -> {
|
||||||
|
lifecycleScope.launch {
|
||||||
// Just do the local cleanup
|
// Just do the local cleanup
|
||||||
Timber.w("Account deactivated, start app")
|
Timber.w("Account deactivated, start app")
|
||||||
sessionHolder.clearActiveSession()
|
sessionHolder.clearActiveSession()
|
||||||
doLocalCleanup(clearPreferences = true)
|
doLocalCleanup(clearPreferences = true)
|
||||||
startNextActivityAndFinish()
|
startNextActivityAndFinish()
|
||||||
}
|
}
|
||||||
args.clearCredentials -> session.signOut(
|
}
|
||||||
!args.isUserLoggedOut,
|
args.clearCredentials -> {
|
||||||
object : MatrixCallback<Unit> {
|
lifecycleScope.launch {
|
||||||
override fun onSuccess(data: Unit) {
|
try {
|
||||||
|
session.signOut(!args.isUserLoggedOut)
|
||||||
Timber.w("SIGN_OUT: success, start app")
|
Timber.w("SIGN_OUT: success, start app")
|
||||||
sessionHolder.clearActiveSession()
|
sessionHolder.clearActiveSession()
|
||||||
doLocalCleanup(clearPreferences = true)
|
doLocalCleanup(clearPreferences = true)
|
||||||
startNextActivityAndFinish()
|
startNextActivityAndFinish()
|
||||||
}
|
} catch (failure: Throwable) {
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
displayError(failure)
|
displayError(failure)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
args.clearCache -> session.clearCache(
|
}
|
||||||
object : MatrixCallback<Unit> {
|
args.clearCache -> {
|
||||||
override fun onSuccess(data: Unit) {
|
lifecycleScope.launch {
|
||||||
|
try {
|
||||||
|
session.clearCache()
|
||||||
doLocalCleanup(clearPreferences = false)
|
doLocalCleanup(clearPreferences = false)
|
||||||
session.startSyncing(applicationContext)
|
session.startSyncing(applicationContext)
|
||||||
startNextActivityAndFinish()
|
startNextActivityAndFinish()
|
||||||
}
|
} catch (failure: Throwable) {
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
displayError(failure)
|
displayError(failure)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,8 +187,7 @@ class MainActivity : VectorBaseActivity<FragmentLoadingBinding>(), UnlockedActiv
|
||||||
Timber.w("Ignoring invalid token global error")
|
Timber.w("Ignoring invalid token global error")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doLocalCleanup(clearPreferences: Boolean) {
|
private suspend fun doLocalCleanup(clearPreferences: Boolean) {
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
|
||||||
// On UI Thread
|
// On UI Thread
|
||||||
Glide.get(this@MainActivity).clearMemory()
|
Glide.get(this@MainActivity).clearMemory()
|
||||||
|
|
||||||
|
@ -206,7 +205,6 @@ class MainActivity : VectorBaseActivity<FragmentLoadingBinding>(), UnlockedActiv
|
||||||
deleteAllFiles(this@MainActivity.cacheDir)
|
deleteAllFiles(this@MainActivity.cacheDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun displayError(failure: Throwable) {
|
private fun displayError(failure: Throwable) {
|
||||||
if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
|
if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.app.features.attachments.preview
|
||||||
|
|
||||||
import android.app.Activity.RESULT_CANCELED
|
import android.app.Activity.RESULT_CANCELED
|
||||||
import android.app.Activity.RESULT_OK
|
import android.app.Activity.RESULT_OK
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -153,8 +154,13 @@ class AttachmentsPreviewFragment @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
private fun applyInsets() {
|
private fun applyInsets() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
activity?.window?.setDecorFitsSystemWindows(false)
|
||||||
|
} else {
|
||||||
view?.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
view?.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
}
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(views.attachmentPreviewerBottomContainer) { v, insets ->
|
ViewCompat.setOnApplyWindowInsetsListener(views.attachmentPreviewerBottomContainer) { v, insets ->
|
||||||
v.updatePadding(bottom = insets.systemWindowInsetBottom)
|
v.updatePadding(bottom = insets.systemWindowInsetBottom)
|
||||||
insets
|
insets
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.auth
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.mvrx.activityViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.showPassword
|
||||||
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.databinding.FragmentReauthConfirmBinding
|
||||||
|
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||||
|
|
||||||
|
class PromptFragment : VectorBaseFragment<FragmentReauthConfirmBinding>() {
|
||||||
|
|
||||||
|
private val viewModel: ReAuthViewModel by activityViewModel()
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
|
||||||
|
FragmentReauthConfirmBinding.inflate(layoutInflater, container, false)
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
views.reAuthConfirmButton.debouncedClicks {
|
||||||
|
onButtonClicked()
|
||||||
|
}
|
||||||
|
views.passwordReveal.debouncedClicks {
|
||||||
|
viewModel.handle(ReAuthActions.StartSSOFallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
views.passwordReveal.debouncedClicks {
|
||||||
|
viewModel.handle(ReAuthActions.TogglePassVisibility)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onButtonClicked() = withState(viewModel) { state ->
|
||||||
|
when (state.flowType) {
|
||||||
|
LoginFlowTypes.SSO -> {
|
||||||
|
viewModel.handle(ReAuthActions.StartSSOFallback)
|
||||||
|
}
|
||||||
|
LoginFlowTypes.PASSWORD -> {
|
||||||
|
val password = views.passwordField.text.toString()
|
||||||
|
if (password.isBlank()) {
|
||||||
|
// Prompt to enter something
|
||||||
|
views.passwordFieldTil.error = getString(R.string.error_empty_field_your_password)
|
||||||
|
} else {
|
||||||
|
views.passwordFieldTil.error = null
|
||||||
|
viewModel.handle(ReAuthActions.ReAuthWithPass(password))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// not supported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) {
|
||||||
|
when (it.flowType) {
|
||||||
|
LoginFlowTypes.SSO -> {
|
||||||
|
views.passwordContainer.isVisible = false
|
||||||
|
views.reAuthConfirmButton.text = getString(R.string.auth_login_sso)
|
||||||
|
}
|
||||||
|
LoginFlowTypes.PASSWORD -> {
|
||||||
|
views.passwordContainer.isVisible = true
|
||||||
|
views.reAuthConfirmButton.text = getString(R.string._continue)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// This login flow is not supported, you should use web?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
views.passwordField.showPassword(it.passwordVisible)
|
||||||
|
|
||||||
|
if (it.passwordVisible) {
|
||||||
|
views.passwordReveal.setImageResource(R.drawable.ic_eye_closed)
|
||||||
|
views.passwordReveal.contentDescription = getString(R.string.a11y_hide_password)
|
||||||
|
} else {
|
||||||
|
views.passwordReveal.setImageResource(R.drawable.ic_eye)
|
||||||
|
views.passwordReveal.contentDescription = getString(R.string.a11y_show_password)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it.lastErrorCode != null) {
|
||||||
|
when (it.flowType) {
|
||||||
|
LoginFlowTypes.SSO -> {
|
||||||
|
views.genericErrorText.isVisible = true
|
||||||
|
views.genericErrorText.text = getString(R.string.authentication_error)
|
||||||
|
}
|
||||||
|
LoginFlowTypes.PASSWORD -> {
|
||||||
|
views.passwordFieldTil.error = getString(R.string.authentication_error)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
views.passwordFieldTil.error = null
|
||||||
|
views.genericErrorText.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.auth
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
|
sealed class ReAuthActions : VectorViewModelAction {
|
||||||
|
object StartSSOFallback : ReAuthActions()
|
||||||
|
object FallBackPageLoaded : ReAuthActions()
|
||||||
|
object FallBackPageClosed : ReAuthActions()
|
||||||
|
object TogglePassVisibility : ReAuthActions()
|
||||||
|
data class ReAuthWithPass(val password: String) : ReAuthActions()
|
||||||
|
}
|
|
@ -0,0 +1,228 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.auth
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.browser.customtabs.CustomTabsCallback
|
||||||
|
import androidx.browser.customtabs.CustomTabsClient
|
||||||
|
import androidx.browser.customtabs.CustomTabsServiceConnection
|
||||||
|
import androidx.browser.customtabs.CustomTabsSession
|
||||||
|
import com.airbnb.mvrx.MvRx
|
||||||
|
import com.airbnb.mvrx.viewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.di.ScreenComponent
|
||||||
|
import im.vector.app.core.extensions.addFragment
|
||||||
|
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||||
|
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||||
|
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||||
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
|
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ReAuthActivity : SimpleFragmentActivity(), ReAuthViewModel.Factory {
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Args(
|
||||||
|
val flowType: String?,
|
||||||
|
val title: String?,
|
||||||
|
val session: String?,
|
||||||
|
val lastErrorCode: String?,
|
||||||
|
val resultKeyStoreAlias: String
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
// For sso
|
||||||
|
private var customTabsServiceConnection: CustomTabsServiceConnection? = null
|
||||||
|
private var customTabsClient: CustomTabsClient? = null
|
||||||
|
private var customTabsSession: CustomTabsSession? = null
|
||||||
|
|
||||||
|
@Inject lateinit var authenticationService: AuthenticationService
|
||||||
|
@Inject lateinit var reAuthViewModelFactory: ReAuthViewModel.Factory
|
||||||
|
|
||||||
|
override fun create(initialState: ReAuthState) = reAuthViewModelFactory.create(initialState)
|
||||||
|
|
||||||
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
|
super.injectWith(injector)
|
||||||
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val sharedViewModel: ReAuthViewModel by viewModel()
|
||||||
|
|
||||||
|
// override fun getTitleRes() = R.string.re_authentication_activity_title
|
||||||
|
|
||||||
|
override fun initUiAndData() {
|
||||||
|
super.initUiAndData()
|
||||||
|
|
||||||
|
val title = intent.extras?.getString(EXTRA_REASON_TITLE) ?: getString(R.string.re_authentication_activity_title)
|
||||||
|
supportActionBar?.setTitle(title) ?: run { setTitle(title) }
|
||||||
|
|
||||||
|
// val authArgs = intent.getParcelableExtra<Args>(MvRx.KEY_ARG)
|
||||||
|
|
||||||
|
// For the sso flow we can for now only rely on the fallback flow, that handles all
|
||||||
|
// the UI, due to the sandbox nature of CCT (chrome custom tab) we cannot get much information
|
||||||
|
// on how the process did go :/
|
||||||
|
// so we assume that after the user close the tab we return success and let caller retry the UIA flow :/
|
||||||
|
if (isFirstCreation()) {
|
||||||
|
addFragment(
|
||||||
|
R.id.container,
|
||||||
|
PromptFragment::class.java
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedViewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
is ReAuthEvents.OpenSsoURl -> {
|
||||||
|
openInCustomTab(it.url)
|
||||||
|
}
|
||||||
|
ReAuthEvents.Dismiss -> {
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
is ReAuthEvents.PasswordFinishSuccess -> {
|
||||||
|
setResult(RESULT_OK, Intent().apply {
|
||||||
|
putExtra(RESULT_FLOW_TYPE, LoginFlowTypes.PASSWORD)
|
||||||
|
putExtra(RESULT_VALUE, it.passwordSafeForIntent)
|
||||||
|
})
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
// It's the only way we have to know if sso falback flow was successful
|
||||||
|
withState(sharedViewModel) {
|
||||||
|
if (it.ssoFallbackPageWasShown) {
|
||||||
|
Timber.d("## UIA ssoFallbackPageWasShown tentative success")
|
||||||
|
setResult(RESULT_OK, Intent().apply {
|
||||||
|
putExtra(RESULT_FLOW_TYPE, LoginFlowTypes.SSO)
|
||||||
|
})
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
|
||||||
|
withState(sharedViewModel) { state ->
|
||||||
|
if (state.ssoFallbackPageWasShown) {
|
||||||
|
sharedViewModel.handle(ReAuthActions.FallBackPageClosed)
|
||||||
|
return@withState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val packageName = CustomTabsClient.getPackageName(this, null)
|
||||||
|
|
||||||
|
// packageName can be null if there are 0 or several CustomTabs compatible browsers installed on the device
|
||||||
|
if (packageName != null) {
|
||||||
|
customTabsServiceConnection = object : CustomTabsServiceConnection() {
|
||||||
|
override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
|
||||||
|
Timber.d("## CustomTab onCustomTabsServiceConnected($name)")
|
||||||
|
customTabsClient = client
|
||||||
|
.also { it.warmup(0L) }
|
||||||
|
customTabsSession = customTabsClient?.newSession(object : CustomTabsCallback() {
|
||||||
|
// override fun onPostMessage(message: String, extras: Bundle?) {
|
||||||
|
// Timber.v("## CustomTab onPostMessage($message)")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onMessageChannelReady(extras: Bundle?) {
|
||||||
|
// Timber.v("## CustomTab onMessageChannelReady()")
|
||||||
|
// }
|
||||||
|
|
||||||
|
override fun onNavigationEvent(navigationEvent: Int, extras: Bundle?) {
|
||||||
|
Timber.v("## CustomTab onNavigationEvent($navigationEvent), $extras")
|
||||||
|
super.onNavigationEvent(navigationEvent, extras)
|
||||||
|
if (navigationEvent == NAVIGATION_FINISHED) {
|
||||||
|
// sharedViewModel.handle(ReAuthActions.FallBackPageLoaded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRelationshipValidationResult(relation: Int, requestedOrigin: Uri, result: Boolean, extras: Bundle?) {
|
||||||
|
Timber.v("## CustomTab onRelationshipValidationResult($relation), $requestedOrigin")
|
||||||
|
super.onRelationshipValidationResult(relation, requestedOrigin, result, extras)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
|
Timber.d("## CustomTab onServiceDisconnected($name)")
|
||||||
|
}
|
||||||
|
}.also {
|
||||||
|
CustomTabsClient.bindCustomTabsService(
|
||||||
|
this,
|
||||||
|
// Despite the API, packageName cannot be null
|
||||||
|
packageName,
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
customTabsServiceConnection?.let { this.unbindService(it) }
|
||||||
|
customTabsServiceConnection = null
|
||||||
|
customTabsSession = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openInCustomTab(ssoUrl: String) {
|
||||||
|
openUrlInChromeCustomTab(this, customTabsSession, ssoUrl)
|
||||||
|
val channelOpened = customTabsSession?.requestPostMessageChannel(Uri.parse("https://element.io"))
|
||||||
|
Timber.d("## CustomTab channelOpened: $channelOpened")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val EXTRA_AUTH_TYPE = "EXTRA_AUTH_TYPE"
|
||||||
|
const val EXTRA_REASON_TITLE = "EXTRA_REASON_TITLE"
|
||||||
|
const val RESULT_FLOW_TYPE = "RESULT_FLOW_TYPE"
|
||||||
|
const val RESULT_VALUE = "RESULT_VALUE"
|
||||||
|
const val DEFAULT_RESULT_KEYSTORE_ALIAS = "ReAuthActivity"
|
||||||
|
|
||||||
|
fun newIntent(context: Context,
|
||||||
|
fromError: RegistrationFlowResponse,
|
||||||
|
lastErrorCode: String?,
|
||||||
|
reasonTitle: String?,
|
||||||
|
resultKeyStoreAlias: String = DEFAULT_RESULT_KEYSTORE_ALIAS): Intent {
|
||||||
|
val authType = when (fromError.nextUncompletedStage()) {
|
||||||
|
LoginFlowTypes.PASSWORD -> {
|
||||||
|
LoginFlowTypes.PASSWORD
|
||||||
|
}
|
||||||
|
LoginFlowTypes.SSO -> {
|
||||||
|
LoginFlowTypes.SSO
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// TODO, support more auth type?
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Intent(context, ReAuthActivity::class.java).apply {
|
||||||
|
putExtra(MvRx.KEY_ARG, Args(authType, reasonTitle, fromError.session, lastErrorCode, resultKeyStoreAlias))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020 New Vector Ltd
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,6 +14,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.core.error
|
package im.vector.app.features.auth
|
||||||
|
|
||||||
class SsoFlowNotSupportedYet : Throwable()
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
|
||||||
|
sealed class ReAuthEvents : VectorViewEvents {
|
||||||
|
data class OpenSsoURl(val url: String) : ReAuthEvents()
|
||||||
|
object Dismiss : ReAuthEvents()
|
||||||
|
data class PasswordFinishSuccess(val passwordSafeForIntent: String) : ReAuthEvents()
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.auth
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
|
||||||
|
data class ReAuthState(
|
||||||
|
val title: String? = null,
|
||||||
|
val session: String? = null,
|
||||||
|
val flowType: String? = null,
|
||||||
|
val ssoFallbackPageWasShown: Boolean = false,
|
||||||
|
val passwordVisible: Boolean = false,
|
||||||
|
val lastErrorCode: String? = null,
|
||||||
|
val resultKeyStoreAlias: String = ""
|
||||||
|
) : MvRxState {
|
||||||
|
constructor(args: ReAuthActivity.Args) : this(
|
||||||
|
args.title,
|
||||||
|
args.session,
|
||||||
|
args.flowType,
|
||||||
|
lastErrorCode = args.lastErrorCode,
|
||||||
|
resultKeyStoreAlias = args.resultKeyStoreAlias
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor() : this(null, null)
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.auth
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
|
class ReAuthViewModel @AssistedInject constructor(
|
||||||
|
@Assisted val initialState: ReAuthState,
|
||||||
|
private val session: Session
|
||||||
|
) : VectorViewModel<ReAuthState, ReAuthActions, ReAuthEvents>(initialState) {
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: ReAuthState): ReAuthViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<ReAuthViewModel, ReAuthState> {
|
||||||
|
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: ReAuthState): ReAuthViewModel? {
|
||||||
|
val factory = when (viewModelContext) {
|
||||||
|
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
|
||||||
|
is ActivityViewModelContext -> viewModelContext.activity as? Factory
|
||||||
|
}
|
||||||
|
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: ReAuthActions) = withState { state ->
|
||||||
|
when (action) {
|
||||||
|
ReAuthActions.StartSSOFallback -> {
|
||||||
|
if (state.flowType == LoginFlowTypes.SSO) {
|
||||||
|
setState { copy(ssoFallbackPageWasShown = true) }
|
||||||
|
val ssoURL = session.getUiaSsoFallbackUrl(initialState.session ?: "")
|
||||||
|
_viewEvents.post(ReAuthEvents.OpenSsoURl(ssoURL))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReAuthActions.FallBackPageLoaded -> {
|
||||||
|
setState { copy(ssoFallbackPageWasShown = true) }
|
||||||
|
}
|
||||||
|
ReAuthActions.FallBackPageClosed -> {
|
||||||
|
// Should we do something here?
|
||||||
|
}
|
||||||
|
ReAuthActions.TogglePassVisibility -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
passwordVisible = !state.passwordVisible
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ReAuthActions.ReAuthWithPass -> {
|
||||||
|
val safeForIntentCypher = ByteArrayOutputStream().also {
|
||||||
|
it.use {
|
||||||
|
session.securelyStoreObject(action.password, initialState.resultKeyStoreAlias, it)
|
||||||
|
}
|
||||||
|
}.toByteArray().toBase64NoPadding()
|
||||||
|
_viewEvents.post(ReAuthEvents.PasswordFinishSuccess(safeForIntentCypher))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ class CallAudioManager(
|
||||||
|
|
||||||
private var savedIsSpeakerPhoneOn = false
|
private var savedIsSpeakerPhoneOn = false
|
||||||
private var savedIsMicrophoneMute = false
|
private var savedIsMicrophoneMute = false
|
||||||
private var savedAudioMode = AudioManager.MODE_INVALID
|
private var savedAudioMode = AudioManager.MODE_NORMAL
|
||||||
|
|
||||||
private var connectedBlueToothHeadset: BluetoothProfile? = null
|
private var connectedBlueToothHeadset: BluetoothProfile? = null
|
||||||
private var wantsBluetoothConnection = false
|
private var wantsBluetoothConnection = false
|
||||||
|
|
|
@ -25,8 +25,11 @@ import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
|
import android.view.WindowInsets
|
||||||
|
import android.view.WindowInsetsController
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
|
@ -102,11 +105,24 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||||
setContentView(R.layout.activity_call)
|
setContentView(R.layout.activity_call)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
private fun hideSystemUI() {
|
private fun hideSystemUI() {
|
||||||
systemUiVisibility = false
|
systemUiVisibility = false
|
||||||
// Enables regular immersive mode.
|
// Enables regular immersive mode.
|
||||||
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
|
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
|
||||||
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
// New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
window.setDecorFitsSystemWindows(false)
|
||||||
|
// New API instead of SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
|
window.decorView.windowInsetsController?.hide(WindowInsets.Type.navigationBars())
|
||||||
|
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE
|
||||||
|
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
||||||
|
// New API instead of FLAG_TRANSLUCENT_STATUS
|
||||||
|
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
|
||||||
|
// New API instead of FLAG_TRANSLUCENT_NAVIGATION
|
||||||
|
window.navigationBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
|
||||||
|
} else {
|
||||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||||
// Set the content to appear under the system bars so that the
|
// Set the content to appear under the system bars so that the
|
||||||
// content doesn't resize when the system bars hide and show.
|
// content doesn't resize when the system bars hide and show.
|
||||||
|
@ -117,15 +133,22 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Shows the system bars by removing all the flags
|
// Shows the system bars by removing all the flags
|
||||||
// except for the ones that make the content appear under the system bars.
|
// except for the ones that make the content appear under the system bars.
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
private fun showSystemUI() {
|
private fun showSystemUI() {
|
||||||
systemUiVisibility = true
|
systemUiVisibility = true
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
// New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
window.setDecorFitsSystemWindows(false)
|
||||||
|
} else {
|
||||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun toggleUiSystemVisibility() {
|
private fun toggleUiSystemVisibility() {
|
||||||
if (systemUiVisibility) {
|
if (systemUiVisibility) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue