Merge branch 'develop' into feature/forward_pagination
This commit is contained in:
commit
e1c6542e03
|
@ -12,12 +12,15 @@
|
||||||
<w>fdroid</w>
|
<w>fdroid</w>
|
||||||
<w>gplay</w>
|
<w>gplay</w>
|
||||||
<w>hmac</w>
|
<w>hmac</w>
|
||||||
|
<w>homeserver</w>
|
||||||
<w>ktlint</w>
|
<w>ktlint</w>
|
||||||
<w>linkified</w>
|
<w>linkified</w>
|
||||||
<w>linkify</w>
|
<w>linkify</w>
|
||||||
<w>megolm</w>
|
<w>megolm</w>
|
||||||
<w>msisdn</w>
|
<w>msisdn</w>
|
||||||
|
<w>msisdns</w>
|
||||||
<w>pbkdf</w>
|
<w>pbkdf</w>
|
||||||
|
<w>pids</w>
|
||||||
<w>pkcs</w>
|
<w>pkcs</w>
|
||||||
<w>riotx</w>
|
<w>riotx</w>
|
||||||
<w>signin</w>
|
<w>signin</w>
|
||||||
|
|
29
CHANGES.md
29
CHANGES.md
|
@ -1,14 +1,17 @@
|
||||||
Changes in RiotX 0.20.0 (2020-XX-XX)
|
Changes in RiotX 0.21.0 (2020-XX-XX)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features ✨:
|
Features ✨:
|
||||||
-
|
- Identity server support (#607)
|
||||||
|
- Switch language support (#41)
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
-
|
- Better connectivity lost indicator when airplane mode is on
|
||||||
|
- Add a setting to hide redacted events (#951)
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- After jump to unread, newer messages are never loaded (#1008)
|
- After jump to unread, newer messages are never loaded (#1008)
|
||||||
|
- Fix issues with FontScale switch (#69, #645)
|
||||||
|
|
||||||
Translations 🗣:
|
Translations 🗣:
|
||||||
-
|
-
|
||||||
|
@ -22,6 +25,26 @@ Build 🧱:
|
||||||
Other changes:
|
Other changes:
|
||||||
-
|
-
|
||||||
|
|
||||||
|
Changes in RiotX 0.20.0 (2020-05-15)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- Add Direct Shortcuts (#652)
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Invite member(s) to an existing room (#1276)
|
||||||
|
- Improve notification accessibility with ticker text (#1226)
|
||||||
|
- Support homeserver discovery from MXID (DISABLED: waiting for design) (#476)
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Fix | Verify Manually by Text crashes if private SSK not known (#1337)
|
||||||
|
- Sometimes the same device appears twice in the list of devices of a user (#1329)
|
||||||
|
- Random Crashes while doing sth with cross signing keys (#1364)
|
||||||
|
- Crash | crash while restoring key backup (#1366)
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
- excludedUserIds parameter added to the UserService.getPagedUsersLive() function
|
||||||
|
|
||||||
Changes in RiotX 0.19.0 (2020-05-04)
|
Changes in RiotX 0.19.0 (2020-05-04)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
# Identity server
|
||||||
|
|
||||||
|
Issue: #607
|
||||||
|
PR: #1354
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
Identity Servers support contact discovery on Matrix by letting people look up Third Party Identifiers to see if the owner has publicly linked them with their Matrix ID.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
The current implementation was Inspired by the code from Riot-Android.
|
||||||
|
|
||||||
|
Difference though (list not exhaustive):
|
||||||
|
- Only API v2 is supported (see https://matrix.org/docs/spec/identity_service/latest)
|
||||||
|
- Homeserver has to be up to date to support binding (Versions.isLoginAndRegistrationSupportedBySdk() has to return true)
|
||||||
|
- The SDK managed the session and client secret when binding ThreePid. Those data are not exposed to the client.
|
||||||
|
- The SDK supports incremental sendAttempt (this is not used by RiotX)
|
||||||
|
- The "Continue" button is now under the information, and not as the same place that the checkbox
|
||||||
|
- The app can cancel a binding. Current data are erased from DB.
|
||||||
|
- The API (IdentityService) is improved.
|
||||||
|
- A new DB to store data related to the identity server management.
|
||||||
|
|
||||||
|
Missing features (list not exhaustive):
|
||||||
|
- Invite by 3Pid (will be in a dedicated PR)
|
||||||
|
- Add email or phone to account (not P1, can be done on Riot-Web)
|
||||||
|
- List email and phone of the account (could be done in a dedicated PR)
|
||||||
|
- Search contact (not P1)
|
||||||
|
- Logout from identity server when user sign out or deactivate his account.
|
||||||
|
|
||||||
|
## Related MSCs
|
||||||
|
The list can be found here: https://matrix.org/blog/2019/09/27/privacy-improvements-in-synapse-1-4-and-riot-1-4
|
||||||
|
|
||||||
|
## Steps and requirements
|
||||||
|
|
||||||
|
- Only one identity server by account can be set. The user's choice is stored in account data with key `m.identity_server`. But every clients will managed its own token to log in to the identity server
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "m.identity_server",
|
||||||
|
"content": {
|
||||||
|
"base_url": "https://matrix.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- The accepted terms are stored in the account data:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "m.accepted_terms",
|
||||||
|
"content": {
|
||||||
|
"accepted": [
|
||||||
|
"https://vector.im/identity-server-privacy-notice-1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Default identity server URL, from Wellknown data is proposed to the user.
|
||||||
|
- Identity server can be set
|
||||||
|
- Identity server can be changed on another user's device, so when the change is detected (thanks to account data sync) RiotX should properly disconnect from a previous identity server (I think it was not the case in Riot-Android, where we keep the token forever)
|
||||||
|
- Registration to the identity server is managed with an openId token
|
||||||
|
- Terms of service can be accepted when configuring the identity server.
|
||||||
|
- Terms of service can be accepted after, if they change.
|
||||||
|
- Identity server can be modified
|
||||||
|
- Identity server can be disconnected with a warning dialog, with special content if there are current bound 3pid on this identity server.
|
||||||
|
- Email can be bound
|
||||||
|
- Email can be unbound
|
||||||
|
- Phone can be bound
|
||||||
|
- Phone can be unbound
|
||||||
|
- Look up can be performed, to get matrixIds from local contact book (phone and email): Android permission correctly handled (not done yet)
|
||||||
|
- Look up pepper can be updated if it is rotated on the identity server
|
||||||
|
- Invitation using 3PID can be done (See #548) (not done yet)
|
||||||
|
- Homeserver access-token will never be sent to an identity server
|
||||||
|
- When user sign-out: logout from the identity server if any.
|
||||||
|
- When user deactivate account: logout from the identity server if any.
|
||||||
|
|
||||||
|
## Screens
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
Identity server settings can be accessed from the internal setting of the application, both from "Discovery" section and from identity detail section.
|
||||||
|
|
||||||
|
### Discovery screen
|
||||||
|
|
||||||
|
This screen displays the identity server configuration and the binding of the user's ThreePid (email and msisdn). This is the main screen of the feature.
|
||||||
|
|
||||||
|
### Set identity server screen
|
||||||
|
|
||||||
|
This screen is a form to set a new identity server URL
|
||||||
|
|
||||||
|
## Ref:
|
||||||
|
- https://matrix.org/blog/2019/09/27/privacy-improvements-in-synapse-1-4-and-riot-1-4 is a good summary of the role of an Identity server and the proper way to configure and use it in respect to the privacy and the consent of the user.
|
||||||
|
- API documentation: https://matrix.org/docs/spec/identity_service/latest
|
||||||
|
- vector.im TOS: https://vector.im/identity-server-privacy-notice
|
|
@ -8,7 +8,7 @@
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
org.gradle.jvmargs=-Xmx1536m
|
org.gradle.jvmargs=-Xmx8192m
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
|
|
@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.api.util.toOptional
|
import im.vector.matrix.android.api.util.toOptional
|
||||||
|
import io.reactivex.Completable
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
|
||||||
|
@ -95,6 +96,10 @@ class RxRoom(private val room: Room) {
|
||||||
fun liveNotificationState(): Observable<RoomNotificationState> {
|
fun liveNotificationState(): Observable<RoomNotificationState> {
|
||||||
return room.getLiveRoomNotificationState().asObservable()
|
return room.getLiveRoomNotificationState().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun invite(userId: String, reason: String? = null): Completable = completableBuilder<Unit> {
|
||||||
|
room.invite(userId, reason, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Room.rx(): RxRoom {
|
fun Room.rx(): RxRoom {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||||
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
@ -90,8 +91,13 @@ class RxSession(private val session: Session) {
|
||||||
return session.getIgnoredUsersLive().asObservable()
|
return session.getIgnoredUsersLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
|
fun livePagedUsers(filter: String? = null, excludedUserIds: Set<String>? = null): Observable<PagedList<User>> {
|
||||||
return session.getPagedUsersLive(filter).asObservable()
|
return session.getPagedUsersLive(filter, excludedUserIds).asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun liveThreePIds(refreshData: Boolean): Observable<List<ThreePid>> {
|
||||||
|
return session.getThreePidsLive(refreshData).asObservable()
|
||||||
|
.startWithCallable { session.getThreePids() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||||
|
|
|
@ -158,6 +158,9 @@ dependencies {
|
||||||
// Bus
|
// Bus
|
||||||
implementation 'org.greenrobot:eventbus:3.1.1'
|
implementation 'org.greenrobot:eventbus:3.1.1'
|
||||||
|
|
||||||
|
// Phone number https://github.com/google/libphonenumber
|
||||||
|
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
|
||||||
|
|
||||||
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
|
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
|
||||||
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
|
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
|
||||||
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
|
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
|
||||||
|
|
|
@ -88,7 +88,8 @@ class CommonTestHelper(context: Context) {
|
||||||
fun syncSession(session: Session) {
|
fun syncSession(session: Session) {
|
||||||
val lock = CountDownLatch(1)
|
val lock = CountDownLatch(1)
|
||||||
|
|
||||||
session.open()
|
GlobalScope.launch(Dispatchers.Main) { session.open() }
|
||||||
|
|
||||||
session.startSync(true)
|
session.startSync(true)
|
||||||
|
|
||||||
val syncLiveData = runBlocking(Dispatchers.Main) {
|
val syncLiveData = runBlocking(Dispatchers.Main) {
|
||||||
|
|
|
@ -248,7 +248,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
assertNotNull(eventWireContent.get("session_id"))
|
assertNotNull(eventWireContent.get("session_id"))
|
||||||
assertNotNull(eventWireContent.get("sender_key"))
|
assertNotNull(eventWireContent.get("sender_key"))
|
||||||
|
|
||||||
assertEquals(senderSession.sessionParams.credentials.deviceId, eventWireContent.get("device_id"))
|
assertEquals(senderSession.sessionParams.deviceId, eventWireContent.get("device_id"))
|
||||||
|
|
||||||
assertNotNull(event.eventId)
|
assertNotNull(event.eventId)
|
||||||
assertEquals(roomId, event.roomId)
|
assertEquals(roomId, event.roomId)
|
||||||
|
|
|
@ -122,7 +122,7 @@ class XSigningTest : InstrumentedTest {
|
||||||
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
||||||
|
|
||||||
val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
|
val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
|
||||||
val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId!!
|
val bobSecondDeviceId = bobSession2.sessionParams.deviceId!!
|
||||||
|
|
||||||
// Check that bob first session sees the new login
|
// Check that bob first session sees the new login
|
||||||
val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||||
|
|
|
@ -148,7 +148,7 @@ class KeyShareTests : InstrumentedTest {
|
||||||
|
|
||||||
// Mark the device as trusted
|
// Mark the device as trusted
|
||||||
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
||||||
aliceSession2.sessionParams.credentials.deviceId ?: "")
|
aliceSession2.sessionParams.deviceId ?: "")
|
||||||
|
|
||||||
// Re request
|
// Re request
|
||||||
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||||
|
@ -253,12 +253,12 @@ class KeyShareTests : InstrumentedTest {
|
||||||
})
|
})
|
||||||
|
|
||||||
val txId: String = "m.testVerif12"
|
val txId: String = "m.testVerif12"
|
||||||
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.credentials.deviceId
|
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
|
||||||
?: "", txId)
|
?: "", txId)
|
||||||
|
|
||||||
mTestHelper.waitWithLatch { latch ->
|
mTestHelper.waitWithLatch { latch ->
|
||||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.credentials.deviceId ?: "")?.isVerified == true
|
aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,13 @@ package im.vector.matrix.android.internal.crypto.keysbackup
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.common.CommonTestHelper
|
import im.vector.matrix.android.common.CommonTestHelper
|
||||||
import im.vector.matrix.android.common.CryptoTestData
|
import im.vector.matrix.android.common.CryptoTestData
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
|
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
|
||||||
*/
|
*/
|
||||||
data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData,
|
data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData,
|
||||||
val aliceKeys: List<OlmInboundGroupSessionWrapper>,
|
val aliceKeys: List<OlmInboundGroupSessionWrapper2>,
|
||||||
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
||||||
val aliceSession2: Session) {
|
val aliceSession2: Session) {
|
||||||
fun cleanUp(testHelper: CommonTestHelper) {
|
fun cleanUp(testHelper: CommonTestHelper) {
|
||||||
|
|
|
@ -835,7 +835,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||||
assertTrue(signature.valid)
|
assertTrue(signature.valid)
|
||||||
assertNotNull(signature.device)
|
assertNotNull(signature.device)
|
||||||
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
|
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
|
||||||
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.credentials.deviceId)
|
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
|
||||||
|
|
||||||
stateObserver.stopAndCheckStates(null)
|
stateObserver.stopAndCheckStates(null)
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
cryptoTestData.cleanUp(mTestHelper)
|
||||||
|
@ -997,7 +997,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||||
keysBackup.backupAllGroupSessions(null, it)
|
keysBackup.backupAllGroupSessions(null, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!!
|
val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId!!
|
||||||
val oldKeyBackupVersion = keysBackup.currentBackupVersion
|
val oldKeyBackupVersion = keysBackup.currentBackupVersion
|
||||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||||
|
|
||||||
|
|
|
@ -579,7 +579,7 @@ class SASTest : InstrumentedTest {
|
||||||
requestID!!,
|
requestID!!,
|
||||||
cryptoTestData.roomId,
|
cryptoTestData.roomId,
|
||||||
bobSession.myUserId,
|
bobSession.myUserId,
|
||||||
bobSession.sessionParams.credentials.deviceId!!,
|
bobSession.sessionParams.deviceId!!,
|
||||||
null)
|
null)
|
||||||
|
|
||||||
bobVerificationService.beginKeyVerificationInDMs(
|
bobVerificationService.beginKeyVerificationInDMs(
|
||||||
|
@ -587,7 +587,7 @@ class SASTest : InstrumentedTest {
|
||||||
requestID!!,
|
requestID!!,
|
||||||
cryptoTestData.roomId,
|
cryptoTestData.roomId,
|
||||||
aliceSession.myUserId,
|
aliceSession.myUserId,
|
||||||
aliceSession.sessionParams.credentials.deviceId!!,
|
aliceSession.sessionParams.deviceId!!,
|
||||||
null)
|
null)
|
||||||
|
|
||||||
// we should reach SHOW SAS on both
|
// we should reach SHOW SAS on both
|
||||||
|
|
|
@ -20,9 +20,9 @@ import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
|
||||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||||
|
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
|
@ -30,13 +30,17 @@ import im.vector.matrix.android.api.util.Cancelable
|
||||||
* This interface defines methods to authenticate or to create an account to a matrix server.
|
* This interface defines methods to authenticate or to create an account to a matrix server.
|
||||||
*/
|
*/
|
||||||
interface AuthenticationService {
|
interface AuthenticationService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request the supported login flows for this homeserver.
|
* Request the supported login flows for this homeserver.
|
||||||
* This is the first method to call to be able to get a wizard to login or the create an account
|
* This is the first method to call to be able to get a wizard to login or the create an account
|
||||||
*/
|
*/
|
||||||
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the supported login flows for the corresponding sessionId.
|
||||||
|
*/
|
||||||
|
fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
|
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
|
||||||
*/
|
*/
|
||||||
|
@ -74,19 +78,26 @@ interface AuthenticationService {
|
||||||
*/
|
*/
|
||||||
fun getLastAuthenticatedSession(): Session?
|
fun getLastAuthenticatedSession(): Session?
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an authenticated session. You should at least call authenticate one time before.
|
|
||||||
* If you logout, this session will no longer be valid.
|
|
||||||
*
|
|
||||||
* @param sessionParams the sessionParams to open with.
|
|
||||||
* @return the associated session if any, or null
|
|
||||||
*/
|
|
||||||
fun getSession(sessionParams: SessionParams): Session?
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a session after a SSO successful login
|
* Create a session after a SSO successful login
|
||||||
*/
|
*/
|
||||||
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
callback: MatrixCallback<Session>): Cancelable
|
callback: MatrixCallback<Session>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a wellknown request, using the domain from the matrixId
|
||||||
|
*/
|
||||||
|
fun getWellKnownData(matrixId: String,
|
||||||
|
callback: MatrixCallback<WellknownResult>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate with a matrixId and a password
|
||||||
|
* Usually call this after a successful call to getWellKnownData()
|
||||||
|
*/
|
||||||
|
fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
|
matrixId: String,
|
||||||
|
password: String,
|
||||||
|
initialDeviceName: String,
|
||||||
|
callback: MatrixCallback<Session>): Cancelable
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,16 +24,38 @@ import im.vector.matrix.android.internal.util.md5
|
||||||
* This data class hold credentials user data.
|
* This data class hold credentials user data.
|
||||||
* You shouldn't have to instantiate it.
|
* You shouldn't have to instantiate it.
|
||||||
* The access token should be use to authenticate user in all server requests.
|
* The access token should be use to authenticate user in all server requests.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Credentials(
|
data class Credentials(
|
||||||
|
/**
|
||||||
|
* The fully-qualified Matrix ID that has been registered.
|
||||||
|
*/
|
||||||
@Json(name = "user_id") val userId: String,
|
@Json(name = "user_id") val userId: String,
|
||||||
@Json(name = "home_server") val homeServer: String,
|
/**
|
||||||
|
* An access token for the account. This access token can then be used to authorize other requests.
|
||||||
|
*/
|
||||||
@Json(name = "access_token") val accessToken: String,
|
@Json(name = "access_token") val accessToken: String,
|
||||||
|
/**
|
||||||
|
* Not documented
|
||||||
|
*/
|
||||||
@Json(name = "refresh_token") val refreshToken: String?,
|
@Json(name = "refresh_token") val refreshToken: String?,
|
||||||
|
/**
|
||||||
|
* The server_name of the homeserver on which the account has been registered.
|
||||||
|
* @Deprecated. Clients should extract the server_name from user_id (by splitting at the first colon)
|
||||||
|
* if they require it. Note also that homeserver is not spelt this way.
|
||||||
|
*/
|
||||||
|
@Json(name = "home_server") val homeServer: String,
|
||||||
|
/**
|
||||||
|
* ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified.
|
||||||
|
*/
|
||||||
@Json(name = "device_id") val deviceId: String?,
|
@Json(name = "device_id") val deviceId: String?,
|
||||||
// Optional data that may contain info to override home server and/or identity server
|
/**
|
||||||
@Json(name = "well_known") val wellKnown: WellKnown? = null
|
* Optional client configuration provided by the server. If present, clients SHOULD use the provided object to
|
||||||
|
* reconfigure themselves, optionally validating the URLs within.
|
||||||
|
* This object takes the same form as the one returned from .well-known autodiscovery.
|
||||||
|
*/
|
||||||
|
@Json(name = "well_known") val discoveryInformation: DiscoveryInformation? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun Credentials.sessionId(): String {
|
internal fun Credentials.sessionId(): String {
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.auth.data
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a light version of Wellknown model, used for login response
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class DiscoveryInformation(
|
||||||
|
/**
|
||||||
|
* Required. Used by clients to discover homeserver information.
|
||||||
|
*/
|
||||||
|
@Json(name = "m.homeserver")
|
||||||
|
val homeServer: WellKnownBaseConfig? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by clients to discover identity server information.
|
||||||
|
* Note: matrix.org does not send this field
|
||||||
|
*/
|
||||||
|
@Json(name = "m.identity_server")
|
||||||
|
val identityServer: WellKnownBaseConfig? = null
|
||||||
|
)
|
|
@ -21,7 +21,48 @@ package im.vector.matrix.android.api.auth.data
|
||||||
* You don't have to manually instantiate it.
|
* You don't have to manually instantiate it.
|
||||||
*/
|
*/
|
||||||
data class SessionParams(
|
data class SessionParams(
|
||||||
|
/**
|
||||||
|
* Please consider using shortcuts instead
|
||||||
|
*/
|
||||||
val credentials: Credentials,
|
val credentials: Credentials,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Please consider using shortcuts instead
|
||||||
|
*/
|
||||||
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to false if the current token is not valid anymore. Application should not have to use this info.
|
||||||
|
*/
|
||||||
val isTokenValid: Boolean
|
val isTokenValid: Boolean
|
||||||
)
|
) {
|
||||||
|
/*
|
||||||
|
* Shortcuts. Usually the application should only need to use these shortcuts
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The userId of the session (Ex: "@user:domain.org")
|
||||||
|
*/
|
||||||
|
val userId = credentials.userId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The deviceId of the session (Ex: "ABCDEFGH")
|
||||||
|
*/
|
||||||
|
val deviceId = credentials.deviceId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current homeserver Url. It can be different that the homeserver url entered
|
||||||
|
* during login phase, because a redirection may have occurred
|
||||||
|
*/
|
||||||
|
val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current homeserver host
|
||||||
|
*/
|
||||||
|
val homeServerHost = homeServerConnectionConfig.homeServerUri.host
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default identity server url if any, returned by the homeserver during login phase
|
||||||
|
*/
|
||||||
|
val defaultIdentityServerUrl = homeServerConnectionConfig.identityServerUri?.toString()
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth.data
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||||
|
@ -52,7 +53,7 @@ data class WellKnown(
|
||||||
val identityServer: WellKnownBaseConfig? = null,
|
val identityServer: WellKnownBaseConfig? = null,
|
||||||
|
|
||||||
@Json(name = "m.integrations")
|
@Json(name = "m.integrations")
|
||||||
val integrations: Map<String, @JvmSuppressWildcards Any>? = null
|
val integrations: JsonDict? = null
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Returns the list of integration managers proposed
|
* Returns the list of integration managers proposed
|
||||||
|
|
|
@ -16,6 +16,6 @@
|
||||||
package im.vector.matrix.android.api.auth.data
|
package im.vector.matrix.android.api.auth.data
|
||||||
|
|
||||||
data class WellKnownManagerConfig(
|
data class WellKnownManagerConfig(
|
||||||
val apiUrl : String,
|
val apiUrl: String,
|
||||||
val uiUrl: String
|
val uiUrl: String
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.auth.wellknown
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.WellKnown
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri
|
||||||
|
*/
|
||||||
|
sealed class WellknownResult {
|
||||||
|
/**
|
||||||
|
* The provided matrixId is no valid. Unable to extract a domain name.
|
||||||
|
*/
|
||||||
|
object InvalidMatrixId : WellknownResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the specific piece of information from the user in a way which fits within the existing client user experience,
|
||||||
|
* if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point.
|
||||||
|
*/
|
||||||
|
data class Prompt(val homeServerUrl: String,
|
||||||
|
val identityServerUrl: String?,
|
||||||
|
val wellKnown: WellKnown) : WellknownResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the current auto-discovery mechanism. If no more auto-discovery mechanisms are available,
|
||||||
|
* then the client may use other methods of determining the required parameters, such as prompting the user, or using default values.
|
||||||
|
*/
|
||||||
|
object Ignore : WellknownResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the user that auto-discovery failed due to invalid/empty data and PROMPT for the parameter.
|
||||||
|
*/
|
||||||
|
object FailPrompt : WellknownResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the user that auto-discovery did not return any usable URLs. Do not continue further with the current login process.
|
||||||
|
* At this point, valid data was obtained, but no homeserver is available to serve the client.
|
||||||
|
* No further guess should be attempted and the user should make a conscientious decision what to do next.
|
||||||
|
*/
|
||||||
|
object FailError : WellknownResult()
|
||||||
|
}
|
|
@ -39,7 +39,10 @@ data class MatrixError(
|
||||||
// For M_LIMIT_EXCEEDED
|
// For M_LIMIT_EXCEEDED
|
||||||
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
|
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
|
||||||
// For M_UNKNOWN_TOKEN
|
// For M_UNKNOWN_TOKEN
|
||||||
@Json(name = "soft_logout") val isSoftLogout: Boolean = false
|
@Json(name = "soft_logout") val isSoftLogout: Boolean = false,
|
||||||
|
// For 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
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -129,6 +132,11 @@ data class MatrixError(
|
||||||
/** (Not documented yet) */
|
/** (Not documented yet) */
|
||||||
const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||||
|
|
||||||
|
const val M_TERMS_NOT_SIGNED = "M_TERMS_NOT_SIGNED"
|
||||||
|
|
||||||
|
// For identity service
|
||||||
|
const val M_INVALID_PEPPER = "M_INVALID_PEPPER"
|
||||||
|
|
||||||
// Possible value for "limit_type"
|
// Possible value for "limit_type"
|
||||||
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.file.FileService
|
import im.vector.matrix.android.api.session.file.FileService
|
||||||
import im.vector.matrix.android.api.session.group.GroupService
|
import im.vector.matrix.android.api.session.group.GroupService
|
||||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityService
|
||||||
import im.vector.matrix.android.api.session.profile.ProfileService
|
import im.vector.matrix.android.api.session.profile.ProfileService
|
||||||
import im.vector.matrix.android.api.session.pushers.PushersService
|
import im.vector.matrix.android.api.session.pushers.PushersService
|
||||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||||
|
@ -39,6 +40,7 @@ import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageSer
|
||||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||||
import im.vector.matrix.android.api.session.sync.FilterService
|
import im.vector.matrix.android.api.session.sync.FilterService
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
|
import im.vector.matrix.android.api.session.terms.TermsService
|
||||||
import im.vector.matrix.android.api.session.user.UserService
|
import im.vector.matrix.android.api.session.user.UserService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,6 +56,7 @@ interface Session :
|
||||||
SignOutService,
|
SignOutService,
|
||||||
FilterService,
|
FilterService,
|
||||||
FileService,
|
FileService,
|
||||||
|
TermsService,
|
||||||
ProfileService,
|
ProfileService,
|
||||||
PushRuleService,
|
PushRuleService,
|
||||||
PushersService,
|
PushersService,
|
||||||
|
@ -77,7 +80,7 @@ interface Session :
|
||||||
* Useful shortcut to get access to the userId
|
* Useful shortcut to get access to the userId
|
||||||
*/
|
*/
|
||||||
val myUserId: String
|
val myUserId: String
|
||||||
get() = sessionParams.credentials.userId
|
get() = sessionParams.userId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The sessionId
|
* The sessionId
|
||||||
|
@ -145,6 +148,11 @@ interface Session :
|
||||||
*/
|
*/
|
||||||
fun cryptoService(): CryptoService
|
fun cryptoService(): CryptoService
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the identity service associated with the session
|
||||||
|
*/
|
||||||
|
fun identityService(): IdentityService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a listener to the session.
|
* Add a listener to the session.
|
||||||
* @param listener the listener to add.
|
* @param listener the listener to add.
|
||||||
|
|
|
@ -24,7 +24,15 @@ data class HomeServerCapabilities(
|
||||||
/**
|
/**
|
||||||
* Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet
|
* Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet
|
||||||
*/
|
*/
|
||||||
val maxUploadFileSize: Long = MAX_UPLOAD_FILE_SIZE_UNKNOWN
|
val maxUploadFileSize: Long = MAX_UPLOAD_FILE_SIZE_UNKNOWN,
|
||||||
|
/**
|
||||||
|
* Last version identity server and binding supported
|
||||||
|
*/
|
||||||
|
val lastVersionIdentityServerSupported: Boolean = false,
|
||||||
|
/**
|
||||||
|
* Default identity server url, provided in Wellknown
|
||||||
|
*/
|
||||||
|
val defaultIdentityServerUrl: String? = null
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.identity
|
||||||
|
|
||||||
|
data class FoundThreePid(
|
||||||
|
val threePid: ThreePid,
|
||||||
|
val matrixId: String
|
||||||
|
)
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to the identity server configuration and services identity server can provide
|
||||||
|
*/
|
||||||
|
interface IdentityService {
|
||||||
|
/**
|
||||||
|
* Return the default identity server of the user, which may have been provided at login time by the homeserver,
|
||||||
|
* or by the Well-known setup of the homeserver
|
||||||
|
* It may be different from the current configured identity server
|
||||||
|
*/
|
||||||
|
fun getDefaultIdentityServer(): String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current identity server URL used by this account. Returns null if no identity server is configured.
|
||||||
|
*/
|
||||||
|
fun getCurrentIdentityServerUrl(): String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the identity server is valid
|
||||||
|
* See https://matrix.org/docs/spec/identity_service/latest#status-check
|
||||||
|
* RiotX SDK only supports identity server API v2
|
||||||
|
*/
|
||||||
|
fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the identity server url.
|
||||||
|
* If successful, any previous identity server will be disconnected.
|
||||||
|
* In case of error, any previous identity server will remain configured.
|
||||||
|
* @param url the new url.
|
||||||
|
* @param callback will notify the user if change is successful. The String will be the final url of the identity server.
|
||||||
|
* The SDK can prepend "https://" for instance.
|
||||||
|
*/
|
||||||
|
fun setNewIdentityServer(url: String, callback: MatrixCallback<String>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect (logout) from the current identity server
|
||||||
|
*/
|
||||||
|
fun disconnect(callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid
|
||||||
|
*/
|
||||||
|
fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will cancel a pending binding of threePid.
|
||||||
|
*/
|
||||||
|
fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will ask the identity server to send an new email or a new SMS to let the user confirm he owns the ThreePid
|
||||||
|
*/
|
||||||
|
fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the code that the identity server has sent to the user (in email or SMS)
|
||||||
|
* Once successful, you will have to call [finalizeBindThreePid]
|
||||||
|
* @param code the code sent to the user
|
||||||
|
*/
|
||||||
|
fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will perform the actual association of ThreePid and Matrix account
|
||||||
|
*/
|
||||||
|
fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unbind a threePid
|
||||||
|
* The request will actually be done on the homeserver
|
||||||
|
*/
|
||||||
|
fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search MatrixId of users providing email and phone numbers
|
||||||
|
*/
|
||||||
|
fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the status of the current user's threePid
|
||||||
|
* A lookup will be performed, but also pending binding state will be restored
|
||||||
|
*
|
||||||
|
* @param threePids the list of threePid the user owns (retrieved form the homeserver)
|
||||||
|
* @param callback onSuccess will be called with a map of ThreePid -> SharedState
|
||||||
|
*/
|
||||||
|
fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable
|
||||||
|
|
||||||
|
fun addListener(listener: IdentityServiceListener)
|
||||||
|
fun removeListener(listener: IdentityServiceListener)
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.identity
|
||||||
|
|
||||||
|
sealed class IdentityServiceError : Throwable() {
|
||||||
|
object OutdatedIdentityServer : IdentityServiceError()
|
||||||
|
object OutdatedHomeServer : IdentityServiceError()
|
||||||
|
object NoIdentityServerConfigured : IdentityServiceError()
|
||||||
|
object TermsNotSignedException : IdentityServiceError()
|
||||||
|
object BulkLookupSha256NotSupported : IdentityServiceError()
|
||||||
|
object BindingError : IdentityServiceError()
|
||||||
|
object NoCurrentBindingError : IdentityServiceError()
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.identity
|
||||||
|
|
||||||
|
interface IdentityServiceListener {
|
||||||
|
fun onIdentityServerChange()
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.identity
|
||||||
|
|
||||||
|
enum class SharedState {
|
||||||
|
SHARED,
|
||||||
|
NOT_SHARED,
|
||||||
|
BINDING_IN_PROGRESS
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.identity
|
||||||
|
|
||||||
|
import com.google.i18n.phonenumbers.NumberParseException
|
||||||
|
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||||
|
import im.vector.matrix.android.internal.session.profile.ThirdPartyIdentifier
|
||||||
|
|
||||||
|
sealed class ThreePid(open val value: String) {
|
||||||
|
data class Email(val email: String) : ThreePid(email)
|
||||||
|
data class Msisdn(val msisdn: String) : ThreePid(msisdn)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ThreePid.toMedium(): String {
|
||||||
|
return when (this) {
|
||||||
|
is ThreePid.Email -> ThirdPartyIdentifier.MEDIUM_EMAIL
|
||||||
|
is ThreePid.Msisdn -> ThirdPartyIdentifier.MEDIUM_MSISDN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(NumberParseException::class)
|
||||||
|
internal fun ThreePid.Msisdn.getCountryCode(): String {
|
||||||
|
return with(PhoneNumberUtil.getInstance()) {
|
||||||
|
getRegionCodeForCountryCode(parse("+$msisdn", null).countryCode)
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,9 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.profile
|
package im.vector.matrix.android.api.session.profile
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
@ -53,4 +55,15 @@ interface ProfileService {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable
|
fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current user 3Pids
|
||||||
|
*/
|
||||||
|
fun getThreePids(): List<ThreePid>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current user 3Pids Live
|
||||||
|
* @param refreshData set to true to fetch data from the homeserver
|
||||||
|
*/
|
||||||
|
fun getThreePidsLive(refreshData: Boolean): LiveData<List<ThreePid>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,10 @@ data class TimelineSettings(
|
||||||
* A flag to filter edit events
|
* A flag to filter edit events
|
||||||
*/
|
*/
|
||||||
val filterEdits: Boolean = false,
|
val filterEdits: Boolean = false,
|
||||||
|
/**
|
||||||
|
* A flag to filter redacted events
|
||||||
|
*/
|
||||||
|
val filterRedacted: Boolean = false,
|
||||||
/**
|
/**
|
||||||
* A flag to filter by types. It should be used with [allowedTypes] field
|
* A flag to filter by types. It should be used with [allowedTypes] field
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.terms
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.session.terms.TermsResponse
|
||||||
|
|
||||||
|
data class GetTermsResponse(
|
||||||
|
val serverResponse: TermsResponse,
|
||||||
|
val alreadyAcceptedTermUrls: Set<String>
|
||||||
|
)
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.session.terms
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
|
interface TermsService {
|
||||||
|
enum class ServiceType {
|
||||||
|
IntegrationManager,
|
||||||
|
IdentityService
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTerms(serviceType: ServiceType,
|
||||||
|
baseUrl: String,
|
||||||
|
callback: MatrixCallback<GetTermsResponse>): Cancelable
|
||||||
|
|
||||||
|
fun agreeToTerms(serviceType: ServiceType,
|
||||||
|
baseUrl: String,
|
||||||
|
agreedUrls: List<String>,
|
||||||
|
token: String?,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
}
|
|
@ -61,9 +61,10 @@ interface UserService {
|
||||||
/**
|
/**
|
||||||
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
|
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
|
||||||
* @param filter the filter. It will look into userId and displayName.
|
* @param filter the filter. It will look into userId and displayName.
|
||||||
|
* @param excludedUserIds userId list which will be excluded from the result list.
|
||||||
* @return a Livedata of users
|
* @return a Livedata of users
|
||||||
*/
|
*/
|
||||||
fun getPagedUsersLive(filter: String? = null): LiveData<PagedList<User>>
|
fun getPagedUsersLive(filter: String? = null, excludedUserIds: Set<String>? = null): LiveData<PagedList<User>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get list of ignored users
|
* Get list of ignored users
|
||||||
|
|
|
@ -22,6 +22,9 @@ package im.vector.matrix.android.api.session.user.model
|
||||||
*/
|
*/
|
||||||
data class User(
|
data class User(
|
||||||
val userId: String,
|
val userId: String,
|
||||||
|
/**
|
||||||
|
* For usage in UI, consider using [getBestName]
|
||||||
|
*/
|
||||||
val displayName: String? = null,
|
val displayName: String? = null,
|
||||||
val avatarUrl: String? = null
|
val avatarUrl: String? = null
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -25,12 +25,15 @@ import im.vector.matrix.android.internal.auth.db.AuthRealmMigration
|
||||||
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
||||||
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
|
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
|
||||||
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
||||||
|
import im.vector.matrix.android.internal.auth.login.DefaultDirectLoginTask
|
||||||
|
import im.vector.matrix.android.internal.auth.login.DirectLoginTask
|
||||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
import im.vector.matrix.android.internal.di.AuthDatabase
|
||||||
|
import im.vector.matrix.android.internal.wellknown.WellknownModule
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@Module
|
@Module(includes = [WellknownModule::class])
|
||||||
internal abstract class AuthModule {
|
internal abstract class AuthModule {
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
@ -59,14 +62,17 @@ internal abstract class AuthModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
|
abstract fun bindSessionParamsStore(store: RealmSessionParamsStore): SessionParamsStore
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindPendingSessionStore(pendingSessionStore: RealmPendingSessionStore): PendingSessionStore
|
abstract fun bindPendingSessionStore(store: RealmPendingSessionStore): PendingSessionStore
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService
|
abstract fun bindAuthenticationService(service: DefaultAuthenticationService): AuthenticationService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSessionCreator(sessionCreator: DefaultSessionCreator): SessionCreator
|
abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,28 +23,33 @@ import im.vector.matrix.android.api.auth.AuthenticationService
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
|
||||||
import im.vector.matrix.android.api.auth.data.Versions
|
import im.vector.matrix.android.api.auth.data.Versions
|
||||||
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
|
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
|
||||||
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
|
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
|
||||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||||
|
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.api.util.NoOpCancellable
|
||||||
import im.vector.matrix.android.internal.SessionManager
|
import im.vector.matrix.android.internal.SessionManager
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||||
import im.vector.matrix.android.internal.auth.data.RiotConfig
|
import im.vector.matrix.android.internal.auth.data.RiotConfig
|
||||||
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
||||||
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
|
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
|
||||||
|
import im.vector.matrix.android.internal.auth.login.DirectLoginTask
|
||||||
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
|
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
|
||||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.task.launchToCallback
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import im.vector.matrix.android.internal.util.exhaustive
|
||||||
import im.vector.matrix.android.internal.util.toCancelable
|
import im.vector.matrix.android.internal.util.toCancelable
|
||||||
import kotlinx.coroutines.GlobalScope
|
import im.vector.matrix.android.internal.wellknown.GetWellknownTask
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
@ -59,7 +64,10 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
private val sessionParamsStore: SessionParamsStore,
|
private val sessionParamsStore: SessionParamsStore,
|
||||||
private val sessionManager: SessionManager,
|
private val sessionManager: SessionManager,
|
||||||
private val sessionCreator: SessionCreator,
|
private val sessionCreator: SessionCreator,
|
||||||
private val pendingSessionStore: PendingSessionStore
|
private val pendingSessionStore: PendingSessionStore,
|
||||||
|
private val getWellknownTask: GetWellknownTask,
|
||||||
|
private val directLoginTask: DirectLoginTask,
|
||||||
|
private val taskExecutor: TaskExecutor
|
||||||
) : AuthenticationService {
|
) : AuthenticationService {
|
||||||
|
|
||||||
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
||||||
|
@ -78,14 +86,21 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSession(sessionParams: SessionParams): Session? {
|
override fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable {
|
||||||
return sessionManager.getOrCreateSession(sessionParams)
|
val homeServerConnectionConfig = sessionParamsStore.get(sessionId)?.homeServerConnectionConfig
|
||||||
|
|
||||||
|
return if (homeServerConnectionConfig == null) {
|
||||||
|
callback.onFailure(IllegalStateException("Session not found"))
|
||||||
|
NoOpCancellable
|
||||||
|
} else {
|
||||||
|
getLoginFlow(homeServerConnectionConfig, callback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
|
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
|
||||||
pendingSessionData = null
|
pendingSessionData = null
|
||||||
|
|
||||||
return GlobalScope.launch(coroutineDispatchers.main) {
|
return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||||
pendingSessionStore.delete()
|
pendingSessionStore.delete()
|
||||||
|
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
|
@ -148,27 +163,71 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||||
|
|
||||||
// Ok, try to get the config.json file of a RiotWeb client
|
// Ok, try to get the config.json file of a RiotWeb client
|
||||||
val riotConfig = executeRequest<RiotConfig>(null) {
|
return runCatching {
|
||||||
apiCall = authAPI.getRiotConfig()
|
executeRequest<RiotConfig>(null) {
|
||||||
}
|
apiCall = authAPI.getRiotConfig()
|
||||||
|
|
||||||
if (riotConfig.defaultHomeServerUrl?.isNotBlank() == true) {
|
|
||||||
// Ok, good sign, we got a default hs url
|
|
||||||
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
|
||||||
homeServerUri = Uri.parse(riotConfig.defaultHomeServerUrl)
|
|
||||||
)
|
|
||||||
|
|
||||||
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
|
|
||||||
|
|
||||||
val versions = executeRequest<Versions>(null) {
|
|
||||||
apiCall = newAuthAPI.versions()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return getLoginFlowResult(newAuthAPI, versions, riotConfig.defaultHomeServerUrl)
|
|
||||||
} else {
|
|
||||||
// Config exists, but there is no default homeserver url (ex: https://riot.im/app)
|
|
||||||
throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
|
|
||||||
}
|
}
|
||||||
|
.map { riotConfig ->
|
||||||
|
if (riotConfig.defaultHomeServerUrl?.isNotBlank() == true) {
|
||||||
|
// Ok, good sign, we got a default hs url
|
||||||
|
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||||
|
homeServerUri = Uri.parse(riotConfig.defaultHomeServerUrl)
|
||||||
|
)
|
||||||
|
|
||||||
|
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
|
||||||
|
|
||||||
|
val versions = executeRequest<Versions>(null) {
|
||||||
|
apiCall = newAuthAPI.versions()
|
||||||
|
}
|
||||||
|
|
||||||
|
getLoginFlowResult(newAuthAPI, versions, riotConfig.defaultHomeServerUrl)
|
||||||
|
} else {
|
||||||
|
// Config exists, but there is no default homeserver url (ex: https://riot.im/app)
|
||||||
|
throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fold(
|
||||||
|
{
|
||||||
|
it
|
||||||
|
},
|
||||||
|
{
|
||||||
|
if (it is Failure.OtherServerError
|
||||||
|
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
|
||||||
|
// Try with wellknown
|
||||||
|
getWellknownLoginFlowInternal(homeServerConnectionConfig)
|
||||||
|
} else {
|
||||||
|
throw it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getWellknownLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||||
|
val domain = homeServerConnectionConfig.homeServerUri.host
|
||||||
|
?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
|
||||||
|
|
||||||
|
// Create a fake userId, for the getWellknown task
|
||||||
|
val fakeUserId = "@alice:$domain"
|
||||||
|
val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId))
|
||||||
|
|
||||||
|
return when (wellknownResult) {
|
||||||
|
is WellknownResult.Prompt -> {
|
||||||
|
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||||
|
homeServerUri = Uri.parse(wellknownResult.homeServerUrl),
|
||||||
|
identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
|
||||||
|
|
||||||
|
val versions = executeRequest<Versions>(null) {
|
||||||
|
apiCall = newAuthAPI.versions()
|
||||||
|
}
|
||||||
|
|
||||||
|
getLoginFlowResult(newAuthAPI, versions, wellknownResult.homeServerUrl)
|
||||||
|
}
|
||||||
|
else -> throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
|
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
|
||||||
|
@ -193,7 +252,8 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
retrofitFactory,
|
retrofitFactory,
|
||||||
coroutineDispatchers,
|
coroutineDispatchers,
|
||||||
sessionCreator,
|
sessionCreator,
|
||||||
pendingSessionStore
|
pendingSessionStore,
|
||||||
|
taskExecutor.executorScope
|
||||||
).also {
|
).also {
|
||||||
currentRegistrationWizard = it
|
currentRegistrationWizard = it
|
||||||
}
|
}
|
||||||
|
@ -213,7 +273,8 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
retrofitFactory,
|
retrofitFactory,
|
||||||
coroutineDispatchers,
|
coroutineDispatchers,
|
||||||
sessionCreator,
|
sessionCreator,
|
||||||
pendingSessionStore
|
pendingSessionStore,
|
||||||
|
taskExecutor.executorScope
|
||||||
).also {
|
).also {
|
||||||
currentLoginWizard = it
|
currentLoginWizard = it
|
||||||
}
|
}
|
||||||
|
@ -230,7 +291,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
pendingSessionData = pendingSessionData?.homeServerConnectionConfig
|
pendingSessionData = pendingSessionData?.homeServerConnectionConfig
|
||||||
?.let { PendingSessionData(it) }
|
?.let { PendingSessionData(it) }
|
||||||
.also {
|
.also {
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
// Should not happen
|
// Should not happen
|
||||||
pendingSessionStore.delete()
|
pendingSessionStore.delete()
|
||||||
|
@ -247,7 +308,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
|
|
||||||
pendingSessionData = null
|
pendingSessionData = null
|
||||||
|
|
||||||
GlobalScope.launch(coroutineDispatchers.main) {
|
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||||
pendingSessionStore.delete()
|
pendingSessionStore.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,11 +316,31 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
callback: MatrixCallback<Session>): Cancelable {
|
callback: MatrixCallback<Session>): Cancelable {
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
createSessionFromSso(credentials, homeServerConnectionConfig)
|
createSessionFromSso(credentials, homeServerConnectionConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getWellKnownData(matrixId: String, callback: MatrixCallback<WellknownResult>): Cancelable {
|
||||||
|
return getWellknownTask
|
||||||
|
.configureWith(GetWellknownTask.Params(matrixId)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
|
matrixId: String,
|
||||||
|
password: String,
|
||||||
|
initialDeviceName: String,
|
||||||
|
callback: MatrixCallback<Session>): Cancelable {
|
||||||
|
return directLoginTask
|
||||||
|
.configureWith(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun createSessionFromSso(credentials: Credentials,
|
private suspend fun createSessionFromSso(credentials: Credentials,
|
||||||
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
|
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
|
||||||
sessionCreator.createSession(credentials, homeServerConnectionConfig)
|
sessionCreator.createSession(credentials, homeServerConnectionConfig)
|
||||||
|
|
|
@ -46,14 +46,14 @@ internal class DefaultSessionCreator @Inject constructor(
|
||||||
val sessionParams = SessionParams(
|
val sessionParams = SessionParams(
|
||||||
credentials = credentials,
|
credentials = credentials,
|
||||||
homeServerConnectionConfig = homeServerConnectionConfig.copy(
|
homeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||||
homeServerUri = credentials.wellKnown?.homeServer?.baseURL
|
homeServerUri = credentials.discoveryInformation?.homeServer?.baseURL
|
||||||
// remove trailing "/"
|
// remove trailing "/"
|
||||||
?.trim { it == '/' }
|
?.trim { it == '/' }
|
||||||
?.takeIf { it.isNotBlank() }
|
?.takeIf { it.isNotBlank() }
|
||||||
?.also { Timber.d("Overriding homeserver url to $it") }
|
?.also { Timber.d("Overriding homeserver url to $it") }
|
||||||
?.let { Uri.parse(it) }
|
?.let { Uri.parse(it) }
|
||||||
?: homeServerConnectionConfig.homeServerUri,
|
?: homeServerConnectionConfig.homeServerUri,
|
||||||
identityServerUri = credentials.wellKnown?.identityServer?.baseURL
|
identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL
|
||||||
// remove trailing "/"
|
// remove trailing "/"
|
||||||
?.trim { it == '/' }
|
?.trim { it == '/' }
|
||||||
?.takeIf { it.isNotBlank() }
|
?.takeIf { it.isNotBlank() }
|
||||||
|
|
|
@ -51,7 +51,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
||||||
}
|
}
|
||||||
return SessionParamsEntity(
|
return SessionParamsEntity(
|
||||||
sessionParams.credentials.sessionId(),
|
sessionParams.credentials.sessionId(),
|
||||||
sessionParams.credentials.userId,
|
sessionParams.userId,
|
||||||
credentialsJson,
|
credentialsJson,
|
||||||
homeServerConnectionConfigJson,
|
homeServerConnectionConfigJson,
|
||||||
sessionParams.isTokenValid)
|
sessionParams.isTokenValid)
|
||||||
|
|
|
@ -38,7 +38,7 @@ import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.launchToCallback
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
|
@ -47,7 +47,8 @@ internal class DefaultLoginWizard(
|
||||||
retrofitFactory: RetrofitFactory,
|
retrofitFactory: RetrofitFactory,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val sessionCreator: SessionCreator,
|
private val sessionCreator: SessionCreator,
|
||||||
private val pendingSessionStore: PendingSessionStore
|
private val pendingSessionStore: PendingSessionStore,
|
||||||
|
private val coroutineScope: CoroutineScope
|
||||||
) : LoginWizard {
|
) : LoginWizard {
|
||||||
|
|
||||||
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
||||||
|
@ -59,7 +60,7 @@ internal class DefaultLoginWizard(
|
||||||
password: String,
|
password: String,
|
||||||
deviceName: String,
|
deviceName: String,
|
||||||
callback: MatrixCallback<Session>): Cancelable {
|
callback: MatrixCallback<Session>): Cancelable {
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
loginInternal(login, password, deviceName)
|
loginInternal(login, password, deviceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +81,7 @@ internal class DefaultLoginWizard(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
|
override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
resetPasswordInternal(email, newPassword)
|
resetPasswordInternal(email, newPassword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +109,7 @@ internal class DefaultLoginWizard(
|
||||||
callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
|
callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
|
||||||
return NoOpCancellable
|
return NoOpCancellable
|
||||||
}
|
}
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
resetPasswordMailConfirmedInternal(safeResetPasswordData)
|
resetPasswordMailConfirmedInternal(safeResetPasswordData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.auth.login
|
||||||
|
|
||||||
|
import dagger.Lazy
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.internal.auth.AuthAPI
|
||||||
|
import im.vector.matrix.android.internal.auth.SessionCreator
|
||||||
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface DirectLoginTask : Task<DirectLoginTask.Params, Session> {
|
||||||
|
data class Params(
|
||||||
|
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
|
val userId: String,
|
||||||
|
val password: String,
|
||||||
|
val deviceName: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultDirectLoginTask @Inject constructor(
|
||||||
|
@Unauthenticated
|
||||||
|
private val okHttpClient: Lazy<OkHttpClient>,
|
||||||
|
private val retrofitFactory: RetrofitFactory,
|
||||||
|
private val sessionCreator: SessionCreator
|
||||||
|
) : DirectLoginTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: DirectLoginTask.Params): Session {
|
||||||
|
val authAPI = retrofitFactory.create(okHttpClient, params.homeServerConnectionConfig.homeServerUri.toString())
|
||||||
|
.create(AuthAPI::class.java)
|
||||||
|
|
||||||
|
val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName)
|
||||||
|
|
||||||
|
val credentials = executeRequest<Credentials>(null) {
|
||||||
|
apiCall = authAPI.login(loginParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionCreator.createSession(credentials, params.homeServerConnectionConfig)
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
||||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.task.launchToCallback
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
|
@ -45,7 +45,8 @@ internal class DefaultRegistrationWizard(
|
||||||
private val retrofitFactory: RetrofitFactory,
|
private val retrofitFactory: RetrofitFactory,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val sessionCreator: SessionCreator,
|
private val sessionCreator: SessionCreator,
|
||||||
private val pendingSessionStore: PendingSessionStore
|
private val pendingSessionStore: PendingSessionStore,
|
||||||
|
private val coroutineScope: CoroutineScope
|
||||||
) : RegistrationWizard {
|
) : RegistrationWizard {
|
||||||
|
|
||||||
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
||||||
|
@ -72,7 +73,7 @@ internal class DefaultRegistrationWizard(
|
||||||
|
|
||||||
override fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable {
|
override fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||||
val params = RegistrationParams()
|
val params = RegistrationParams()
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
performRegistrationRequest(params)
|
performRegistrationRequest(params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +87,7 @@ internal class DefaultRegistrationWizard(
|
||||||
password = password,
|
password = password,
|
||||||
initialDeviceDisplayName = initialDeviceDisplayName
|
initialDeviceDisplayName = initialDeviceDisplayName
|
||||||
)
|
)
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
performRegistrationRequest(params)
|
performRegistrationRequest(params)
|
||||||
.also {
|
.also {
|
||||||
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
|
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
|
||||||
|
@ -101,7 +102,7 @@ internal class DefaultRegistrationWizard(
|
||||||
return NoOpCancellable
|
return NoOpCancellable
|
||||||
}
|
}
|
||||||
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
|
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
performRegistrationRequest(params)
|
performRegistrationRequest(params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,13 +113,13 @@ internal class DefaultRegistrationWizard(
|
||||||
return NoOpCancellable
|
return NoOpCancellable
|
||||||
}
|
}
|
||||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
|
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
performRegistrationRequest(params)
|
performRegistrationRequest(params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
|
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
|
||||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||||
|
|
||||||
|
@ -131,7 +132,7 @@ internal class DefaultRegistrationWizard(
|
||||||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||||
return NoOpCancellable
|
return NoOpCancellable
|
||||||
}
|
}
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
sendThreePid(safeCurrentThreePid)
|
sendThreePid(safeCurrentThreePid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,13 +178,13 @@ internal class DefaultRegistrationWizard(
|
||||||
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
|
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
|
||||||
return NoOpCancellable
|
return NoOpCancellable
|
||||||
}
|
}
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
performRegistrationRequest(safeParam, delayMillis)
|
performRegistrationRequest(safeParam, delayMillis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
override fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
validateThreePid(code)
|
validateThreePid(code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,7 +200,7 @@ internal class DefaultRegistrationWizard(
|
||||||
code = code
|
code = code
|
||||||
)
|
)
|
||||||
val validationResponse = validateCodeTask.execute(ValidateCodeTask.Params(url, validationBody))
|
val validationResponse = validateCodeTask.execute(ValidateCodeTask.Params(url, validationBody))
|
||||||
if (validationResponse.success == true) {
|
if (validationResponse.isSuccess()) {
|
||||||
// The entered code is correct
|
// The entered code is correct
|
||||||
// Same than validate email
|
// Same than validate email
|
||||||
return performRegistrationRequest(registrationParams, 3_000)
|
return performRegistrationRequest(registrationParams, 3_000)
|
||||||
|
@ -214,7 +215,7 @@ internal class DefaultRegistrationWizard(
|
||||||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||||
return NoOpCancellable
|
return NoOpCancellable
|
||||||
}
|
}
|
||||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
|
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
|
||||||
performRegistrationRequest(params)
|
performRegistrationRequest(params)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.androidsdk.rest.model.login
|
package im.vector.matrix.android.internal.auth.registration
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
|
@ -18,9 +18,12 @@ package im.vector.matrix.android.internal.auth.registration
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.extensions.orFalse
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class SuccessResult(
|
data class SuccessResult(
|
||||||
@Json(name = "success")
|
@Json(name = "success")
|
||||||
val success: Boolean?
|
val success: Boolean?
|
||||||
)
|
) {
|
||||||
|
fun isSuccess() = success.orFalse()
|
||||||
|
}
|
||||||
|
|
|
@ -446,7 +446,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||||
return cryptoStore.getUserDevices(userId)?.map { it.value }?.sortedBy { it.deviceId } ?: emptyList()
|
return cryptoStore.getUserDeviceList(userId) ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
|
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
|
||||||
|
|
|
@ -21,7 +21,7 @@ import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
@ -488,7 +488,7 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
forwardingCurve25519KeyChain: List<String>,
|
forwardingCurve25519KeyChain: List<String>,
|
||||||
keysClaimed: Map<String, String>,
|
keysClaimed: Map<String, String>,
|
||||||
exportFormat: Boolean): Boolean {
|
exportFormat: Boolean): Boolean {
|
||||||
val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat)
|
val session = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat)
|
||||||
runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
|
runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
|
||||||
.fold(
|
.fold(
|
||||||
{
|
{
|
||||||
|
@ -543,18 +543,18 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
* @param megolmSessionsData the megolm sessions data
|
* @param megolmSessionsData the megolm sessions data
|
||||||
* @return the successfully imported sessions.
|
* @return the successfully imported sessions.
|
||||||
*/
|
*/
|
||||||
fun importInboundGroupSessions(megolmSessionsData: List<MegolmSessionData>): List<OlmInboundGroupSessionWrapper> {
|
fun importInboundGroupSessions(megolmSessionsData: List<MegolmSessionData>): List<OlmInboundGroupSessionWrapper2> {
|
||||||
val sessions = ArrayList<OlmInboundGroupSessionWrapper>(megolmSessionsData.size)
|
val sessions = ArrayList<OlmInboundGroupSessionWrapper2>(megolmSessionsData.size)
|
||||||
|
|
||||||
for (megolmSessionData in megolmSessionsData) {
|
for (megolmSessionData in megolmSessionsData) {
|
||||||
val sessionId = megolmSessionData.sessionId
|
val sessionId = megolmSessionData.sessionId
|
||||||
val senderKey = megolmSessionData.senderKey
|
val senderKey = megolmSessionData.senderKey
|
||||||
val roomId = megolmSessionData.roomId
|
val roomId = megolmSessionData.roomId
|
||||||
|
|
||||||
var session: OlmInboundGroupSessionWrapper? = null
|
var session: OlmInboundGroupSessionWrapper2? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session = OlmInboundGroupSessionWrapper(megolmSessionData)
|
session = OlmInboundGroupSessionWrapper2(megolmSessionData)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
Timber.e(e, "## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
||||||
}
|
}
|
||||||
|
@ -741,7 +741,7 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
* @param senderKey the base64-encoded curve25519 key of the sender.
|
* @param senderKey the base64-encoded curve25519 key of the sender.
|
||||||
* @return the inbound group session.
|
* @return the inbound group session.
|
||||||
*/
|
*/
|
||||||
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper {
|
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper2 {
|
||||||
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
|
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ internal class SetDeviceVerificationAction @Inject constructor(
|
||||||
|
|
||||||
if (device.trustLevel != trustLevel) {
|
if (device.trustLevel != trustLevel) {
|
||||||
device.trustLevel = trustLevel
|
device.trustLevel = trustLevel
|
||||||
cryptoStore.storeUserDevice(userId, device)
|
cryptoStore.setDeviceTrust(userId, deviceId, trustLevel.crossSigningVerified, trustLevel.locallyVerified)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,7 +199,7 @@ internal object MXEncryptedAttachments {
|
||||||
.replace('_', '/')
|
.replace('_', '/')
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun base64ToBase64Url(base64: String): String {
|
internal fun base64ToBase64Url(base64: String): String {
|
||||||
return base64.replace("\n".toRegex(), "")
|
return base64.replace("\n".toRegex(), "")
|
||||||
.replace("\\+".toRegex(), "-")
|
.replace("\\+".toRegex(), "-")
|
||||||
.replace('/', '_')
|
.replace('/', '_')
|
||||||
|
|
|
@ -66,7 +66,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBacku
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
|
import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
|
import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
|
||||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.SavedKeyBackupKeyInfo
|
import im.vector.matrix.android.internal.crypto.store.SavedKeyBackupKeyInfo
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
|
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
|
||||||
|
@ -1318,7 +1318,7 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun encryptGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper): KeyBackupData {
|
fun encryptGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2): KeyBackupData {
|
||||||
// Gather information for each key
|
// Gather information for each key
|
||||||
val device = cryptoStore.deviceWithIdentityKey(olmInboundGroupSessionWrapper.senderKey!!)
|
val device = cryptoStore.deviceWithIdentityKey(olmInboundGroupSessionWrapper.senderKey!!)
|
||||||
|
|
||||||
|
|
|
@ -103,11 +103,10 @@ class OlmInboundGroupSessionWrapper : Serializable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export the inbound group session keys
|
* Export the inbound group session keys
|
||||||
* @param index the index to export. If null, the first known index will be used
|
|
||||||
*
|
*
|
||||||
* @return the inbound group session as MegolmSessionData if the operation succeeds
|
* @return the inbound group session as MegolmSessionData if the operation succeeds
|
||||||
*/
|
*/
|
||||||
fun exportKeys(index: Long? = null): MegolmSessionData? {
|
fun exportKeys(): MegolmSessionData? {
|
||||||
return try {
|
return try {
|
||||||
if (null == forwardingCurve25519KeyChain) {
|
if (null == forwardingCurve25519KeyChain) {
|
||||||
forwardingCurve25519KeyChain = ArrayList()
|
forwardingCurve25519KeyChain = ArrayList()
|
||||||
|
@ -117,8 +116,6 @@ class OlmInboundGroupSessionWrapper : Serializable {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val wantedIndex = index ?: olmInboundGroupSession!!.firstKnownIndex
|
|
||||||
|
|
||||||
MegolmSessionData(
|
MegolmSessionData(
|
||||||
senderClaimedEd25519Key = keysClaimed?.get("ed25519"),
|
senderClaimedEd25519Key = keysClaimed?.get("ed25519"),
|
||||||
forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!),
|
forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!),
|
||||||
|
@ -126,7 +123,7 @@ class OlmInboundGroupSessionWrapper : Serializable {
|
||||||
senderClaimedKeys = keysClaimed,
|
senderClaimedKeys = keysClaimed,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
sessionId = olmInboundGroupSession!!.sessionIdentifier(),
|
sessionId = olmInboundGroupSession!!.sessionIdentifier(),
|
||||||
sessionKey = olmInboundGroupSession!!.export(wantedIndex),
|
sessionKey = olmInboundGroupSession!!.export(olmInboundGroupSession!!.firstKnownIndex),
|
||||||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
|
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
|
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
||||||
|
import org.matrix.olm.OlmInboundGroupSession
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class adds more context to a OlmInboundGroupSession object.
|
||||||
|
* This allows additional checks. The class implements Serializable so that the context can be stored.
|
||||||
|
*/
|
||||||
|
class OlmInboundGroupSessionWrapper2 : Serializable {
|
||||||
|
|
||||||
|
// The associated olm inbound group session.
|
||||||
|
var olmInboundGroupSession: OlmInboundGroupSession? = null
|
||||||
|
|
||||||
|
// The room in which this session is used.
|
||||||
|
var roomId: String? = null
|
||||||
|
|
||||||
|
// The base64-encoded curve25519 key of the sender.
|
||||||
|
var senderKey: String? = null
|
||||||
|
|
||||||
|
// Other keys the sender claims.
|
||||||
|
var keysClaimed: Map<String, String>? = null
|
||||||
|
|
||||||
|
// Devices which forwarded this session to us (normally empty).
|
||||||
|
var forwardingCurve25519KeyChain: List<String>? = ArrayList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the first known message index
|
||||||
|
*/
|
||||||
|
val firstKnownIndex: Long?
|
||||||
|
get() {
|
||||||
|
if (null != olmInboundGroupSession) {
|
||||||
|
try {
|
||||||
|
return olmInboundGroupSession!!.firstKnownIndex
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "## getFirstKnownIndex() : getFirstKnownIndex failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param sessionKey the session key
|
||||||
|
* @param isImported true if it is an imported session key
|
||||||
|
*/
|
||||||
|
constructor(sessionKey: String, isImported: Boolean) {
|
||||||
|
try {
|
||||||
|
if (!isImported) {
|
||||||
|
olmInboundGroupSession = OlmInboundGroupSession(sessionKey)
|
||||||
|
} else {
|
||||||
|
olmInboundGroupSession = OlmInboundGroupSession.importSession(sessionKey)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "Cannot create")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a new instance from the provided keys map.
|
||||||
|
*
|
||||||
|
* @param megolmSessionData the megolm session data
|
||||||
|
* @throws Exception if the data are invalid
|
||||||
|
*/
|
||||||
|
@Throws(Exception::class)
|
||||||
|
constructor(megolmSessionData: MegolmSessionData) {
|
||||||
|
try {
|
||||||
|
olmInboundGroupSession = OlmInboundGroupSession.importSession(megolmSessionData.sessionKey!!)
|
||||||
|
|
||||||
|
if (olmInboundGroupSession!!.sessionIdentifier() != megolmSessionData.sessionId) {
|
||||||
|
throw Exception("Mismatched group session Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
senderKey = megolmSessionData.senderKey
|
||||||
|
keysClaimed = megolmSessionData.senderClaimedKeys
|
||||||
|
roomId = megolmSessionData.roomId
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw Exception(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export the inbound group session keys
|
||||||
|
* @param index the index to export. If null, the first known index will be used
|
||||||
|
*
|
||||||
|
* @return the inbound group session as MegolmSessionData if the operation succeeds
|
||||||
|
*/
|
||||||
|
fun exportKeys(index: Long? = null): MegolmSessionData? {
|
||||||
|
return try {
|
||||||
|
if (null == forwardingCurve25519KeyChain) {
|
||||||
|
forwardingCurve25519KeyChain = ArrayList()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keysClaimed == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val wantedIndex = index ?: olmInboundGroupSession!!.firstKnownIndex
|
||||||
|
|
||||||
|
MegolmSessionData(
|
||||||
|
senderClaimedEd25519Key = keysClaimed?.get("ed25519"),
|
||||||
|
forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!),
|
||||||
|
senderKey = senderKey,
|
||||||
|
senderClaimedKeys = keysClaimed,
|
||||||
|
roomId = roomId,
|
||||||
|
sessionId = olmInboundGroupSession!!.sessionIdentifier(),
|
||||||
|
sessionKey = olmInboundGroupSession!!.export(wantedIndex),
|
||||||
|
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "## export() : senderKey $senderKey failed")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export the session for a message index.
|
||||||
|
*
|
||||||
|
* @param messageIndex the message index
|
||||||
|
* @return the exported data
|
||||||
|
*/
|
||||||
|
fun exportSession(messageIndex: Long): String? {
|
||||||
|
if (null != olmInboundGroupSession) {
|
||||||
|
try {
|
||||||
|
return olmInboundGroupSession!!.export(messageIndex)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "## exportSession() : export failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
||||||
import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest
|
import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest
|
||||||
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
||||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
@ -59,7 +59,7 @@ internal interface IMXCryptoStore {
|
||||||
*
|
*
|
||||||
* @return the list of all known group sessions, to export them.
|
* @return the list of all known group sessions, to export them.
|
||||||
*/
|
*/
|
||||||
fun getInboundGroupSessions(): List<OlmInboundGroupSessionWrapper>
|
fun getInboundGroupSessions(): List<OlmInboundGroupSessionWrapper2>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true to unilaterally blacklist all unverified devices.
|
* @return true to unilaterally blacklist all unverified devices.
|
||||||
|
@ -164,14 +164,6 @@ internal interface IMXCryptoStore {
|
||||||
*/
|
*/
|
||||||
fun saveOlmAccount()
|
fun saveOlmAccount()
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a device for a user.
|
|
||||||
*
|
|
||||||
* @param userId the user's id.
|
|
||||||
* @param device the device to store.
|
|
||||||
*/
|
|
||||||
fun storeUserDevice(userId: String?, deviceInfo: CryptoDeviceInfo?)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a device for a user.
|
* Retrieve a device for a user.
|
||||||
*
|
*
|
||||||
|
@ -282,7 +274,7 @@ internal interface IMXCryptoStore {
|
||||||
*
|
*
|
||||||
* @param sessions the inbound group sessions to store.
|
* @param sessions the inbound group sessions to store.
|
||||||
*/
|
*/
|
||||||
fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper>)
|
fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper2>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve an inbound group session.
|
* Retrieve an inbound group session.
|
||||||
|
@ -291,7 +283,7 @@ internal interface IMXCryptoStore {
|
||||||
* @param senderKey the base64-encoded curve25519 key of the sender.
|
* @param senderKey the base64-encoded curve25519 key of the sender.
|
||||||
* @return an inbound group session.
|
* @return an inbound group session.
|
||||||
*/
|
*/
|
||||||
fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper?
|
fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an inbound group session
|
* Remove an inbound group session
|
||||||
|
@ -315,7 +307,7 @@ internal interface IMXCryptoStore {
|
||||||
*
|
*
|
||||||
* @param sessions the sessions
|
* @param sessions the sessions
|
||||||
*/
|
*/
|
||||||
fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<OlmInboundGroupSessionWrapper>)
|
fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<OlmInboundGroupSessionWrapper2>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve inbound group sessions that are not yet backed up.
|
* Retrieve inbound group sessions that are not yet backed up.
|
||||||
|
@ -323,7 +315,7 @@ internal interface IMXCryptoStore {
|
||||||
* @param limit the maximum number of sessions to return.
|
* @param limit the maximum number of sessions to return.
|
||||||
* @return an array of non backed up inbound group sessions.
|
* @return an array of non backed up inbound group sessions.
|
||||||
*/
|
*/
|
||||||
fun inboundGroupSessionsToBackup(limit: Int): List<OlmInboundGroupSessionWrapper>
|
fun inboundGroupSessionsToBackup(limit: Int): List<OlmInboundGroupSessionWrapper2>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of stored inbound group sessions.
|
* Number of stored inbound group sessions.
|
||||||
|
@ -415,7 +407,7 @@ internal interface IMXCryptoStore {
|
||||||
fun getKeyBackupRecoveryKeyInfo() : SavedKeyBackupKeyInfo?
|
fun getKeyBackupRecoveryKeyInfo() : SavedKeyBackupKeyInfo?
|
||||||
|
|
||||||
fun setUserKeysAsTrusted(userId: String, trusted: Boolean = true)
|
fun setUserKeysAsTrusted(userId: String, trusted: Boolean = true)
|
||||||
fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean)
|
fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?)
|
||||||
|
|
||||||
fun clearOtherUserTrust()
|
fun clearOtherUserTrust()
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest
|
||||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
||||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
@ -108,7 +108,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
private val olmSessionsToRelease = HashMap<String, OlmSessionWrapper>()
|
private val olmSessionsToRelease = HashMap<String, OlmSessionWrapper>()
|
||||||
|
|
||||||
// Cache for InboundGroupSession, to release them properly
|
// Cache for InboundGroupSession, to release them properly
|
||||||
private val inboundGroupSessionToRelease = HashMap<String, OlmInboundGroupSessionWrapper>()
|
private val inboundGroupSessionToRelease = HashMap<String, OlmInboundGroupSessionWrapper2>()
|
||||||
|
|
||||||
private val newSessionListeners = ArrayList<NewSessionListener>()
|
private val newSessionListeners = ArrayList<NewSessionListener>()
|
||||||
|
|
||||||
|
@ -233,29 +233,6 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
return olmAccount!!
|
return olmAccount!!
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun storeUserDevice(userId: String?, deviceInfo: CryptoDeviceInfo?) {
|
|
||||||
if (userId == null || deviceInfo == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
doRealmTransaction(realmConfiguration) { realm ->
|
|
||||||
val user = UserEntity.getOrCreate(realm, userId)
|
|
||||||
|
|
||||||
// Create device info
|
|
||||||
val deviceInfoEntity = CryptoMapper.mapToEntity(deviceInfo)
|
|
||||||
realm.insertOrUpdate(deviceInfoEntity)
|
|
||||||
// val deviceInfoEntity = DeviceInfoEntity.getOrCreate(it, userId, deviceInfo.deviceId).apply {
|
|
||||||
// deviceId = deviceInfo.deviceId
|
|
||||||
// identityKey = deviceInfo.identityKey()
|
|
||||||
// putDeviceInfo(deviceInfo)
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!user.devices.contains(deviceInfoEntity)) {
|
|
||||||
user.devices.add(deviceInfoEntity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo? {
|
override fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo? {
|
||||||
return doWithRealm(realmConfiguration) {
|
return doWithRealm(realmConfiguration) {
|
||||||
it.where<DeviceInfoEntity>()
|
it.where<DeviceInfoEntity>()
|
||||||
|
@ -654,7 +631,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
.toMutableSet()
|
.toMutableSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper>) {
|
override fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper2>) {
|
||||||
if (sessions.isEmpty()) {
|
if (sessions.isEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -692,7 +669,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper? {
|
override fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2? {
|
||||||
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
|
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
|
||||||
|
|
||||||
// If not in cache (or not found), try to read it from realm
|
// If not in cache (or not found), try to read it from realm
|
||||||
|
@ -712,10 +689,10 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper,
|
* Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2,
|
||||||
* so there is no need to use or update `inboundGroupSessionToRelease` for native memory management
|
* so there is no need to use or update `inboundGroupSessionToRelease` for native memory management
|
||||||
*/
|
*/
|
||||||
override fun getInboundGroupSessions(): MutableList<OlmInboundGroupSessionWrapper> {
|
override fun getInboundGroupSessions(): MutableList<OlmInboundGroupSessionWrapper2> {
|
||||||
return doWithRealm(realmConfiguration) {
|
return doWithRealm(realmConfiguration) {
|
||||||
it.where<OlmInboundGroupSessionEntity>()
|
it.where<OlmInboundGroupSessionEntity>()
|
||||||
.findAll()
|
.findAll()
|
||||||
|
@ -787,7 +764,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<OlmInboundGroupSessionWrapper>) {
|
override fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<OlmInboundGroupSessionWrapper2>) {
|
||||||
if (olmInboundGroupSessionWrappers.isEmpty()) {
|
if (olmInboundGroupSessionWrappers.isEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -810,7 +787,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inboundGroupSessionsToBackup(limit: Int): List<OlmInboundGroupSessionWrapper> {
|
override fun inboundGroupSessionsToBackup(limit: Int): List<OlmInboundGroupSessionWrapper2> {
|
||||||
return doWithRealm(realmConfiguration) {
|
return doWithRealm(realmConfiguration) {
|
||||||
it.where<OlmInboundGroupSessionEntity>()
|
it.where<OlmInboundGroupSessionEntity>()
|
||||||
.equalTo(OlmInboundGroupSessionEntityFields.BACKED_UP, false)
|
.equalTo(OlmInboundGroupSessionEntityFields.BACKED_UP, false)
|
||||||
|
@ -1276,7 +1253,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean) {
|
override fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?) {
|
||||||
doRealmTransaction(realmConfiguration) { realm ->
|
doRealmTransaction(realmConfiguration) { realm ->
|
||||||
realm.where(DeviceInfoEntity::class.java)
|
realm.where(DeviceInfoEntity::class.java)
|
||||||
.equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId))
|
.equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId))
|
||||||
|
@ -1289,7 +1266,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
deviceInfoEntity.trustLevelEntity = it
|
deviceInfoEntity.trustLevelEntity = it
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
trustEntity.locallyVerified = locallyVerified
|
locallyVerified?.let { trustEntity.locallyVerified = it }
|
||||||
trustEntity.crossSignedVerified = crossSignedVerified
|
trustEntity.crossSignedVerified = crossSignedVerified
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1429,7 +1406,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
// Just override existing, caller should check and untrust id needed
|
// Just override existing, caller should check and untrust id needed
|
||||||
val existing = CrossSigningInfoEntity.getOrCreate(realm, userId)
|
val existing = CrossSigningInfoEntity.getOrCreate(realm, userId)
|
||||||
existing.crossSigningKeys.forEach { it.deleteFromRealm() }
|
existing.crossSigningKeys.deleteAllFromRealm()
|
||||||
existing.crossSigningKeys.addAll(
|
existing.crossSigningKeys.addAll(
|
||||||
info.crossSigningKeys.map {
|
info.crossSigningKeys.map {
|
||||||
crossSigningKeysMapper.map(it)
|
crossSigningKeysMapper.map(it)
|
||||||
|
|
|
@ -21,6 +21,8 @@ import com.squareup.moshi.Types
|
||||||
import im.vector.matrix.android.api.extensions.tryThis
|
import im.vector.matrix.android.api.extensions.tryThis
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.mapper.CrossSigningKeysMapper
|
import im.vector.matrix.android.internal.crypto.store.db.mapper.CrossSigningKeysMapper
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntityFields
|
import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntityFields
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEntityFields
|
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEntityFields
|
||||||
|
@ -29,6 +31,7 @@ import im.vector.matrix.android.internal.crypto.store.db.model.GossipingEventEnt
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
|
import im.vector.matrix.android.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntityFields
|
import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntityFields
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
|
import im.vector.matrix.android.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
|
import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntityFields
|
import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntityFields
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields
|
import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields
|
||||||
|
@ -42,7 +45,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
|
|
||||||
// Version 1L added Cross Signing info persistence
|
// Version 1L added Cross Signing info persistence
|
||||||
companion object {
|
companion object {
|
||||||
const val CRYPTO_STORE_SCHEMA_VERSION = 5L
|
const val CRYPTO_STORE_SCHEMA_VERSION = 6L
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||||
|
@ -53,6 +56,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
if (oldVersion <= 2) migrateTo3(realm)
|
if (oldVersion <= 2) migrateTo3(realm)
|
||||||
if (oldVersion <= 3) migrateTo4(realm)
|
if (oldVersion <= 3) migrateTo4(realm)
|
||||||
if (oldVersion <= 4) migrateTo5(realm)
|
if (oldVersion <= 4) migrateTo5(realm)
|
||||||
|
if (oldVersion <= 5) migrateTo6(realm)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun migrateTo1(realm: DynamicRealm) {
|
private fun migrateTo1(realm: DynamicRealm) {
|
||||||
|
@ -216,6 +220,23 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
}
|
}
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migrate frozen classes
|
||||||
|
val inboundGroupSessions = realm.where("OlmInboundGroupSessionEntity").findAll()
|
||||||
|
inboundGroupSessions.forEach { dynamicObject ->
|
||||||
|
dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { serializedObject ->
|
||||||
|
try {
|
||||||
|
deserializeFromRealm<OlmInboundGroupSessionWrapper?>(serializedObject)?.let { oldFormat ->
|
||||||
|
val newFormat = oldFormat.exportKeys()?.let {
|
||||||
|
OlmInboundGroupSessionWrapper2(it)
|
||||||
|
}
|
||||||
|
dynamicObject.setString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA, serializeForRealm(newFormat))
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure, "## OlmInboundGroupSessionEntity migration failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun migrateTo5(realm: DynamicRealm) {
|
private fun migrateTo5(realm: DynamicRealm) {
|
||||||
|
@ -238,4 +259,22 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixes duplicate devices in UserEntity#devices
|
||||||
|
private fun migrateTo6(realm: DynamicRealm) {
|
||||||
|
val userEntities = realm.where("UserEntity").findAll()
|
||||||
|
userEntities.forEach {
|
||||||
|
try {
|
||||||
|
val deviceList = it.getList(UserEntityFields.DEVICES.`$`)
|
||||||
|
?: return@forEach
|
||||||
|
val distinct = deviceList.distinctBy { it.getString(DeviceInfoEntityFields.DEVICE_ID) }
|
||||||
|
if (distinct.size != deviceList.size) {
|
||||||
|
deviceList.clear()
|
||||||
|
deviceList.addAll(distinct)
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.w(failure, "Crypto Data base migration error for migrateTo6")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.store.db.model
|
package im.vector.matrix.android.internal.crypto.store.db.model
|
||||||
|
|
||||||
import im.vector.matrix.android.api.extensions.tryThis
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
|
import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
|
import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: String?, senderKey: String?) = "$sessionId|$senderKey"
|
internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: String?, senderKey: String?) = "$sessionId|$senderKey"
|
||||||
|
|
||||||
|
@ -36,11 +36,16 @@ internal open class OlmInboundGroupSessionEntity(
|
||||||
var backedUp: Boolean = false)
|
var backedUp: Boolean = false)
|
||||||
: RealmObject() {
|
: RealmObject() {
|
||||||
|
|
||||||
fun getInboundGroupSession(): OlmInboundGroupSessionWrapper? {
|
fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? {
|
||||||
return tryThis { deserializeFromRealm<OlmInboundGroupSessionWrapper?>(olmInboundGroupSessionData) }
|
return try {
|
||||||
|
deserializeFromRealm<OlmInboundGroupSessionWrapper2?>(olmInboundGroupSessionData)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure, "## Deserialization failure")
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun putInboundGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper?) {
|
fun putInboundGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2?) {
|
||||||
olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper)
|
olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.tools
|
||||||
import org.matrix.olm.OlmPkDecryption
|
import org.matrix.olm.OlmPkDecryption
|
||||||
import org.matrix.olm.OlmPkEncryption
|
import org.matrix.olm.OlmPkEncryption
|
||||||
import org.matrix.olm.OlmPkSigning
|
import org.matrix.olm.OlmPkSigning
|
||||||
|
import org.matrix.olm.OlmUtility
|
||||||
|
|
||||||
fun <T> withOlmEncryption(block: (OlmPkEncryption) -> T): T {
|
fun <T> withOlmEncryption(block: (OlmPkEncryption) -> T): T {
|
||||||
val olmPkEncryption = OlmPkEncryption()
|
val olmPkEncryption = OlmPkEncryption()
|
||||||
|
@ -46,3 +47,12 @@ fun <T> withOlmSigning(block: (OlmPkSigning) -> T): T {
|
||||||
olmPkSigning.releaseSigning()
|
olmPkSigning.releaseSigning()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> withOlmUtility(block: (OlmUtility) -> T): T {
|
||||||
|
val olmUtility = OlmUtility()
|
||||||
|
try {
|
||||||
|
return block(olmUtility)
|
||||||
|
} finally {
|
||||||
|
olmUtility.releaseUtility()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -121,7 +121,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// val requestMessage = KeyVerificationRequest(
|
// val requestMessage = KeyVerificationRequest(
|
||||||
// fromDevice = session.sessionParams.credentials.deviceId ?: "",
|
// fromDevice = session.sessionParams.deviceId ?: "",
|
||||||
// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
|
||||||
// timestamp = System.currentTimeMillis().toInt(),
|
// timestamp = System.currentTimeMillis().toInt(),
|
||||||
// transactionId = transactionId
|
// transactionId = transactionId
|
||||||
|
|
|
@ -81,9 +81,9 @@ import im.vector.matrix.android.internal.crypto.verification.qrcode.generateShar
|
||||||
import im.vector.matrix.android.internal.di.DeviceId
|
import im.vector.matrix.android.internal.di.DeviceId
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
@ -104,7 +104,8 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
private val verificationTransportRoomMessageFactory: VerificationTransportRoomMessageFactory,
|
private val verificationTransportRoomMessageFactory: VerificationTransportRoomMessageFactory,
|
||||||
private val verificationTransportToDeviceFactory: VerificationTransportToDeviceFactory,
|
private val verificationTransportToDeviceFactory: VerificationTransportToDeviceFactory,
|
||||||
private val crossSigningService: CrossSigningService,
|
private val crossSigningService: CrossSigningService,
|
||||||
private val cryptoCoroutineScope: CoroutineScope
|
private val cryptoCoroutineScope: CoroutineScope,
|
||||||
|
private val taskExecutor: TaskExecutor
|
||||||
) : DefaultVerificationTransaction.Listener, VerificationService {
|
) : DefaultVerificationTransaction.Listener, VerificationService {
|
||||||
|
|
||||||
private val uiHandler = Handler(Looper.getMainLooper())
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
|
@ -161,7 +162,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onRoomEvent(event: Event) {
|
fun onRoomEvent(event: Event) {
|
||||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
when (event.getClearType()) {
|
when (event.getClearType()) {
|
||||||
EventType.KEY_VERIFICATION_START -> {
|
EventType.KEY_VERIFICATION_START -> {
|
||||||
onRoomStartRequestReceived(event)
|
onRoomStartRequestReceived(event)
|
||||||
|
@ -301,7 +302,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
// We don't want to block here
|
// We don't want to block here
|
||||||
val otherDeviceId = validRequestInfo.fromDevice
|
val otherDeviceId = validRequestInfo.fromDevice
|
||||||
|
|
||||||
GlobalScope.launch {
|
cryptoCoroutineScope.launch {
|
||||||
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
|
if (checkKeysAreDownloaded(senderId, otherDeviceId) == null) {
|
||||||
Timber.e("## Verification device $otherDeviceId is not known")
|
Timber.e("## Verification device $otherDeviceId is not known")
|
||||||
}
|
}
|
||||||
|
@ -340,7 +341,7 @@ internal class DefaultVerificationService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't want to block here
|
// We don't want to block here
|
||||||
GlobalScope.launch {
|
taskExecutor.executorScope.launch {
|
||||||
if (checkKeysAreDownloaded(senderId, validRequestInfo.fromDevice) == null) {
|
if (checkKeysAreDownloaded(senderId, validRequestInfo.fromDevice) == null) {
|
||||||
Timber.e("## SAS Verification device ${validRequestInfo.fromDevice} is not known")
|
Timber.e("## SAS Verification device ${validRequestInfo.fromDevice} is not known")
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,10 +48,11 @@ import im.vector.matrix.android.internal.di.SessionId
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||||
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.util.StringProvider
|
import im.vector.matrix.android.internal.util.StringProvider
|
||||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
@ -66,7 +67,8 @@ internal class VerificationTransportRoomMessage(
|
||||||
private val userDeviceId: String?,
|
private val userDeviceId: String?,
|
||||||
private val roomId: String,
|
private val roomId: String,
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
private val tx: DefaultVerificationTransaction?
|
private val tx: DefaultVerificationTransaction?,
|
||||||
|
private val coroutineScope: CoroutineScope
|
||||||
) : VerificationTransport {
|
) : VerificationTransport {
|
||||||
|
|
||||||
override fun <T> sendToOther(type: String,
|
override fun <T> sendToOther(type: String,
|
||||||
|
@ -131,7 +133,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO listen to DB to get synced info
|
// TODO listen to DB to get synced info
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
coroutineScope.launch(Dispatchers.Main) {
|
||||||
workLiveData.observeForever(observer)
|
workLiveData.observeForever(observer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,7 +214,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO listen to DB to get synced info
|
// TODO listen to DB to get synced info
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
coroutineScope.launch(Dispatchers.Main) {
|
||||||
workLiveData.observeForever(observer)
|
workLiveData.observeForever(observer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -265,7 +267,7 @@ internal class VerificationTransportRoomMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO listen to DB to get synced info
|
// TODO listen to DB to get synced info
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
coroutineScope.launch(Dispatchers.Main) {
|
||||||
workLiveData.observeForever(observer)
|
workLiveData.observeForever(observer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -384,9 +386,19 @@ internal class VerificationTransportRoomMessageFactory @Inject constructor(
|
||||||
private val userId: String,
|
private val userId: String,
|
||||||
@DeviceId
|
@DeviceId
|
||||||
private val deviceId: String?,
|
private val deviceId: String?,
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory) {
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
|
private val taskExecutor: TaskExecutor
|
||||||
|
) {
|
||||||
|
|
||||||
fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage {
|
fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage {
|
||||||
return VerificationTransportRoomMessage(workManagerProvider, stringProvider, sessionId, userId, deviceId, roomId, localEchoEventFactory, tx)
|
return VerificationTransportRoomMessage(workManagerProvider,
|
||||||
|
stringProvider,
|
||||||
|
sessionId,
|
||||||
|
userId,
|
||||||
|
deviceId,
|
||||||
|
roomId,
|
||||||
|
localEchoEventFactory,
|
||||||
|
tx,
|
||||||
|
taskExecutor.executorScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,21 +20,16 @@ import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
|
||||||
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity
|
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HomeServerCapabilitiesEntity <-> HomeSeverCapabilities
|
* HomeServerCapabilitiesEntity -> HomeSeverCapabilities
|
||||||
*/
|
*/
|
||||||
internal object HomeServerCapabilitiesMapper {
|
internal object HomeServerCapabilitiesMapper {
|
||||||
|
|
||||||
fun map(entity: HomeServerCapabilitiesEntity): HomeServerCapabilities {
|
fun map(entity: HomeServerCapabilitiesEntity): HomeServerCapabilities {
|
||||||
return HomeServerCapabilities(
|
return HomeServerCapabilities(
|
||||||
canChangePassword = entity.canChangePassword,
|
canChangePassword = entity.canChangePassword,
|
||||||
maxUploadFileSize = entity.maxUploadFileSize
|
maxUploadFileSize = entity.maxUploadFileSize,
|
||||||
)
|
lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
|
||||||
}
|
defaultIdentityServerUrl = entity.defaultIdentityServerUrl
|
||||||
|
|
||||||
fun map(domain: HomeServerCapabilities): HomeServerCapabilitiesEntity {
|
|
||||||
return HomeServerCapabilitiesEntity(
|
|
||||||
canChangePassword = domain.canChangePassword,
|
|
||||||
maxUploadFileSize = domain.maxUploadFileSize
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ import io.realm.RealmObject
|
||||||
internal open class HomeServerCapabilitiesEntity(
|
internal open class HomeServerCapabilitiesEntity(
|
||||||
var canChangePassword: Boolean = true,
|
var canChangePassword: Boolean = true,
|
||||||
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
|
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
|
||||||
|
var lastVersionIdentityServerSupported: Boolean = false,
|
||||||
|
var defaultIdentityServerUrl: String? = null,
|
||||||
var lastUpdatedTimestamp: Long = 0L
|
var lastUpdatedTimestamp: Long = 0L
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ import io.realm.annotations.RealmModule
|
||||||
UserEntity::class,
|
UserEntity::class,
|
||||||
IgnoredUserEntity::class,
|
IgnoredUserEntity::class,
|
||||||
BreadcrumbsEntity::class,
|
BreadcrumbsEntity::class,
|
||||||
|
UserThreePidEntity::class,
|
||||||
EventAnnotationsSummaryEntity::class,
|
EventAnnotationsSummaryEntity::class,
|
||||||
ReactionAggregatedSummaryEntity::class,
|
ReactionAggregatedSummaryEntity::class,
|
||||||
EditAggregatedSummaryEntity::class,
|
EditAggregatedSummaryEntity::class,
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmObject
|
||||||
|
|
||||||
|
internal open class UserThreePidEntity(
|
||||||
|
var medium: String = "",
|
||||||
|
var address: String = "",
|
||||||
|
var validatedAt: Long = 0,
|
||||||
|
var addedAt: Long = 0
|
||||||
|
) : RealmObject()
|
|
@ -62,8 +62,8 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
||||||
val liveEvents = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes)
|
val liveEvents = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes)
|
||||||
if (filterContentRelation) {
|
if (filterContentRelation) {
|
||||||
liveEvents
|
liveEvents
|
||||||
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
|
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
|
||||||
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.RESPONSE_TYPE)
|
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
|
||||||
}
|
}
|
||||||
val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) {
|
val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) {
|
||||||
sendingTimelineEvents
|
sendingTimelineEvents
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.database.query
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query strings used to filter the timeline events regarding the Json raw string of the Event
|
||||||
|
*/
|
||||||
|
internal object TimelineEventFilter {
|
||||||
|
/**
|
||||||
|
* To apply to Event.content
|
||||||
|
*/
|
||||||
|
internal object Content {
|
||||||
|
internal const val EDIT = """{*"m.relates_to"*"rel_type":*"m.replace"*}"""
|
||||||
|
internal const val RESPONSE = """{*"m.relates_to"*"rel_type":*"m.response"*}"""
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To apply to Event.unsigned
|
||||||
|
*/
|
||||||
|
internal object Unsigned {
|
||||||
|
internal const val REDACTED = """{*"redacted_because":*}"""
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,8 +20,12 @@ import javax.inject.Qualifier
|
||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class Authenticated
|
internal annotation class Authenticated
|
||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class Unauthenticated
|
internal annotation class AuthenticatedIdentity
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
internal annotation class Unauthenticated
|
||||||
|
|
|
@ -20,12 +20,16 @@ import javax.inject.Qualifier
|
||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class AuthDatabase
|
internal annotation class AuthDatabase
|
||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class SessionDatabase
|
internal annotation class SessionDatabase
|
||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class CryptoDatabase
|
internal annotation class CryptoDatabase
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
internal annotation class IdentityDatabase
|
||||||
|
|
|
@ -20,16 +20,16 @@ import javax.inject.Qualifier
|
||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class SessionFilesDirectory
|
internal annotation class SessionFilesDirectory
|
||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class SessionCacheDirectory
|
internal annotation class SessionCacheDirectory
|
||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class CacheDirectory
|
internal annotation class CacheDirectory
|
||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class ExternalFilesDirectory
|
internal annotation class ExternalFilesDirectory
|
||||||
|
|
|
@ -16,20 +16,16 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.network
|
package im.vector.matrix.android.internal.network
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
||||||
import im.vector.matrix.android.internal.di.SessionId
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class AccessTokenInterceptor @Inject constructor(
|
internal class AccessTokenInterceptor(private val accessTokenProvider: AccessTokenProvider) : Interceptor {
|
||||||
@SessionId private val sessionId: String,
|
|
||||||
private val sessionParamsStore: SessionParamsStore) : Interceptor {
|
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
var request = chain.request()
|
var request = chain.request()
|
||||||
|
|
||||||
accessToken?.let {
|
accessTokenProvider.getToken()?.let {
|
||||||
val newRequestBuilder = request.newBuilder()
|
val newRequestBuilder = request.newBuilder()
|
||||||
// Add the access token to all requests if it is set
|
// Add the access token to all requests if it is set
|
||||||
newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer $it")
|
newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer $it")
|
||||||
|
@ -38,7 +34,4 @@ internal class AccessTokenInterceptor @Inject constructor(
|
||||||
|
|
||||||
return chain.proceed(request)
|
return chain.proceed(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val accessToken
|
|
||||||
get() = sessionParamsStore.get(sessionId)?.credentials?.accessToken
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,4 +26,11 @@ internal object NetworkConstants {
|
||||||
// Media
|
// Media
|
||||||
private const val URI_API_MEDIA_PREFIX_PATH = "_matrix/media"
|
private const val URI_API_MEDIA_PREFIX_PATH = "_matrix/media"
|
||||||
const val URI_API_MEDIA_PREFIX_PATH_R0 = "$URI_API_MEDIA_PREFIX_PATH/r0/"
|
const val URI_API_MEDIA_PREFIX_PATH_R0 = "$URI_API_MEDIA_PREFIX_PATH/r0/"
|
||||||
|
|
||||||
|
// Identity server
|
||||||
|
const val URI_IDENTITY_PREFIX_PATH = "_matrix/identity/v2"
|
||||||
|
const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/"
|
||||||
|
|
||||||
|
// TODO Ganfra, use correct value
|
||||||
|
const val URI_INTEGRATION_MANAGER_PATH = "TODO/"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.network.httpclient
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
|
||||||
|
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
|
||||||
|
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
|
internal fun OkHttpClient.addAccessTokenInterceptor(accessTokenProvider: AccessTokenProvider): OkHttpClient {
|
||||||
|
return newBuilder()
|
||||||
|
.apply {
|
||||||
|
// Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor
|
||||||
|
val existingCurlInterceptors = interceptors().filterIsInstance<CurlLoggingInterceptor>()
|
||||||
|
interceptors().removeAll(existingCurlInterceptors)
|
||||||
|
|
||||||
|
addInterceptor(AccessTokenInterceptor(accessTokenProvider))
|
||||||
|
|
||||||
|
// Re add eventually the curl logging interceptors
|
||||||
|
existingCurlInterceptors.forEach {
|
||||||
|
addInterceptor(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.network.token
|
||||||
|
|
||||||
|
internal interface AccessTokenProvider {
|
||||||
|
fun getToken(): String?
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.network.token
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
|
import im.vector.matrix.android.internal.di.SessionId
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class HomeserverAccessTokenProvider @Inject constructor(
|
||||||
|
@SessionId private val sessionId: String,
|
||||||
|
private val sessionParamsStore: SessionParamsStore
|
||||||
|
) : AccessTokenProvider {
|
||||||
|
override fun getToken() = sessionParamsStore.get(sessionId)?.credentials?.accessToken
|
||||||
|
}
|
|
@ -28,10 +28,10 @@ import im.vector.matrix.android.internal.di.ExternalFilesDirectory
|
||||||
import im.vector.matrix.android.internal.di.SessionCacheDirectory
|
import im.vector.matrix.android.internal.di.SessionCacheDirectory
|
||||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.toCancelable
|
import im.vector.matrix.android.internal.util.toCancelable
|
||||||
import im.vector.matrix.android.internal.util.writeToFile
|
import im.vector.matrix.android.internal.util.writeToFile
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
@ -51,7 +51,9 @@ internal class DefaultFileService @Inject constructor(
|
||||||
private val contentUrlResolver: ContentUrlResolver,
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
@Unauthenticated
|
@Unauthenticated
|
||||||
private val okHttpClient: OkHttpClient,
|
private val okHttpClient: OkHttpClient,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers) : FileService {
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
private val taskExecutor: TaskExecutor
|
||||||
|
) : FileService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download file in the cache folder, and eventually decrypt it
|
* Download file in the cache folder, and eventually decrypt it
|
||||||
|
@ -63,7 +65,7 @@ internal class DefaultFileService @Inject constructor(
|
||||||
url: String?,
|
url: String?,
|
||||||
elementToDecrypt: ElementToDecrypt?,
|
elementToDecrypt: ElementToDecrypt?,
|
||||||
callback: MatrixCallback<File>): Cancelable {
|
callback: MatrixCallback<File>): Cancelable {
|
||||||
return GlobalScope.launch(coroutineDispatchers.main) {
|
return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||||
withContext(coroutineDispatchers.io) {
|
withContext(coroutineDispatchers.io) {
|
||||||
Try {
|
Try {
|
||||||
val folder = File(sessionCacheDirectory, "MF")
|
val folder = File(sessionCacheDirectory, "MF")
|
||||||
|
|
|
@ -43,6 +43,7 @@ import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageSer
|
||||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||||
import im.vector.matrix.android.api.session.sync.FilterService
|
import im.vector.matrix.android.api.session.sync.FilterService
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
|
import im.vector.matrix.android.api.session.terms.TermsService
|
||||||
import im.vector.matrix.android.api.session.user.UserService
|
import im.vector.matrix.android.api.session.user.UserService
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||||
|
@ -50,12 +51,14 @@ import im.vector.matrix.android.internal.crypto.crosssigning.ShieldTrustUpdater
|
||||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.di.SessionId
|
import im.vector.matrix.android.internal.di.SessionId
|
||||||
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||||
|
import im.vector.matrix.android.internal.session.identity.DefaultIdentityService
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor
|
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecryptor
|
||||||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||||
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
||||||
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
|
@ -82,6 +85,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
private val signOutService: Lazy<SignOutService>,
|
private val signOutService: Lazy<SignOutService>,
|
||||||
private val pushRuleService: Lazy<PushRuleService>,
|
private val pushRuleService: Lazy<PushRuleService>,
|
||||||
private val pushersService: Lazy<PushersService>,
|
private val pushersService: Lazy<PushersService>,
|
||||||
|
private val termsService: Lazy<TermsService>,
|
||||||
private val cryptoService: Lazy<DefaultCryptoService>,
|
private val cryptoService: Lazy<DefaultCryptoService>,
|
||||||
private val fileService: Lazy<FileService>,
|
private val fileService: Lazy<FileService>,
|
||||||
private val secureStorageService: Lazy<SecureStorageService>,
|
private val secureStorageService: Lazy<SecureStorageService>,
|
||||||
|
@ -97,8 +101,11 @@ internal class DefaultSession @Inject constructor(
|
||||||
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
||||||
private val accountService: Lazy<AccountService>,
|
private val accountService: Lazy<AccountService>,
|
||||||
private val timelineEventDecryptor: TimelineEventDecryptor,
|
private val timelineEventDecryptor: TimelineEventDecryptor,
|
||||||
private val shieldTrustUpdater: ShieldTrustUpdater)
|
private val shieldTrustUpdater: ShieldTrustUpdater,
|
||||||
: Session,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
private val defaultIdentityService: DefaultIdentityService,
|
||||||
|
private val taskExecutor: TaskExecutor
|
||||||
|
) : Session,
|
||||||
RoomService by roomService.get(),
|
RoomService by roomService.get(),
|
||||||
RoomDirectoryService by roomDirectoryService.get(),
|
RoomDirectoryService by roomDirectoryService.get(),
|
||||||
GroupService by groupService.get(),
|
GroupService by groupService.get(),
|
||||||
|
@ -108,6 +115,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
PushRuleService by pushRuleService.get(),
|
PushRuleService by pushRuleService.get(),
|
||||||
PushersService by pushersService.get(),
|
PushersService by pushersService.get(),
|
||||||
FileService by fileService.get(),
|
FileService by fileService.get(),
|
||||||
|
TermsService by termsService.get(),
|
||||||
InitialSyncProgressService by initialSyncProgressService.get(),
|
InitialSyncProgressService by initialSyncProgressService.get(),
|
||||||
SecureStorageService by secureStorageService.get(),
|
SecureStorageService by secureStorageService.get(),
|
||||||
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
|
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
|
||||||
|
@ -133,6 +141,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
eventBus.register(this)
|
eventBus.register(this)
|
||||||
timelineEventDecryptor.start()
|
timelineEventDecryptor.start()
|
||||||
shieldTrustUpdater.start()
|
shieldTrustUpdater.start()
|
||||||
|
defaultIdentityService.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requireBackgroundSync() {
|
override fun requireBackgroundSync() {
|
||||||
|
@ -175,6 +184,10 @@ internal class DefaultSession @Inject constructor(
|
||||||
isOpen = false
|
isOpen = false
|
||||||
eventBus.unregister(this)
|
eventBus.unregister(this)
|
||||||
shieldTrustUpdater.stop()
|
shieldTrustUpdater.stop()
|
||||||
|
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||||
|
// This has to be done on main thread
|
||||||
|
defaultIdentityService.stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSyncStateLive(): LiveData<SyncState> {
|
override fun getSyncStateLive(): LiveData<SyncState> {
|
||||||
|
@ -204,7 +217,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
if (globalError is GlobalError.InvalidToken
|
if (globalError is GlobalError.InvalidToken
|
||||||
&& globalError.softLogout) {
|
&& globalError.softLogout) {
|
||||||
// Mark the token has invalid
|
// Mark the token has invalid
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
taskExecutor.executorScope.launch(Dispatchers.IO) {
|
||||||
sessionParamsStore.setTokenInvalid(sessionId)
|
sessionParamsStore.setTokenInvalid(sessionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,6 +231,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
|
|
||||||
override fun cryptoService(): CryptoService = cryptoService.get()
|
override fun cryptoService(): CryptoService = cryptoService.get()
|
||||||
|
|
||||||
|
override fun identityService() = defaultIdentityService
|
||||||
|
|
||||||
override fun addListener(listener: Session.Listener) {
|
override fun addListener(listener: Session.Listener) {
|
||||||
sessionListeners.addListener(listener)
|
sessionListeners.addListener(listener)
|
||||||
}
|
}
|
||||||
|
@ -228,6 +243,6 @@ internal class DefaultSession @Inject constructor(
|
||||||
|
|
||||||
// For easy debugging
|
// For easy debugging
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "$myUserId - ${sessionParams.credentials.deviceId}"
|
return "$myUserId - ${sessionParams.deviceId}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@ import im.vector.matrix.android.internal.session.filter.FilterModule
|
||||||
import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
|
import im.vector.matrix.android.internal.session.group.GetGroupDataWorker
|
||||||
import im.vector.matrix.android.internal.session.group.GroupModule
|
import im.vector.matrix.android.internal.session.group.GroupModule
|
||||||
import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule
|
import im.vector.matrix.android.internal.session.homeserver.HomeServerCapabilitiesModule
|
||||||
|
import im.vector.matrix.android.internal.session.identity.IdentityModule
|
||||||
|
import im.vector.matrix.android.internal.session.openid.OpenIdModule
|
||||||
import im.vector.matrix.android.internal.session.profile.ProfileModule
|
import im.vector.matrix.android.internal.session.profile.ProfileModule
|
||||||
import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker
|
import im.vector.matrix.android.internal.session.pushers.AddHttpPusherWorker
|
||||||
import im.vector.matrix.android.internal.session.pushers.PushersModule
|
import im.vector.matrix.android.internal.session.pushers.PushersModule
|
||||||
|
@ -50,6 +52,7 @@ import im.vector.matrix.android.internal.session.sync.SyncModule
|
||||||
import im.vector.matrix.android.internal.session.sync.SyncTask
|
import im.vector.matrix.android.internal.session.sync.SyncTask
|
||||||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||||
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
||||||
|
import im.vector.matrix.android.internal.session.terms.TermsModule
|
||||||
import im.vector.matrix.android.internal.session.user.UserModule
|
import im.vector.matrix.android.internal.session.user.UserModule
|
||||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
|
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
@ -70,6 +73,9 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
CacheModule::class,
|
CacheModule::class,
|
||||||
CryptoModule::class,
|
CryptoModule::class,
|
||||||
PushersModule::class,
|
PushersModule::class,
|
||||||
|
OpenIdModule::class,
|
||||||
|
IdentityModule::class,
|
||||||
|
TermsModule::class,
|
||||||
AccountDataModule::class,
|
AccountDataModule::class,
|
||||||
ProfileModule::class,
|
ProfileModule::class,
|
||||||
SessionAssistedInjectModule::class,
|
SessionAssistedInjectModule::class,
|
||||||
|
|
|
@ -50,14 +50,15 @@ import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.di.UserMd5
|
import im.vector.matrix.android.internal.di.UserMd5
|
||||||
import im.vector.matrix.android.internal.eventbus.EventBusTimberLogger
|
import im.vector.matrix.android.internal.eventbus.EventBusTimberLogger
|
||||||
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
|
|
||||||
import im.vector.matrix.android.internal.network.DefaultNetworkConnectivityChecker
|
import im.vector.matrix.android.internal.network.DefaultNetworkConnectivityChecker
|
||||||
import im.vector.matrix.android.internal.network.FallbackNetworkCallbackStrategy
|
import im.vector.matrix.android.internal.network.FallbackNetworkCallbackStrategy
|
||||||
import im.vector.matrix.android.internal.network.NetworkCallbackStrategy
|
import im.vector.matrix.android.internal.network.NetworkCallbackStrategy
|
||||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
||||||
import im.vector.matrix.android.internal.network.PreferredNetworkCallbackStrategy
|
import im.vector.matrix.android.internal.network.PreferredNetworkCallbackStrategy
|
||||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
|
import im.vector.matrix.android.internal.network.httpclient.addAccessTokenInterceptor
|
||||||
|
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
||||||
|
import im.vector.matrix.android.internal.network.token.HomeserverAccessTokenProvider
|
||||||
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
||||||
import im.vector.matrix.android.internal.session.homeserver.DefaultHomeServerCapabilitiesService
|
import im.vector.matrix.android.internal.session.homeserver.DefaultHomeServerCapabilitiesService
|
||||||
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
|
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
|
||||||
|
@ -175,21 +176,8 @@ internal abstract class SessionModule {
|
||||||
@SessionScope
|
@SessionScope
|
||||||
@Authenticated
|
@Authenticated
|
||||||
fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
|
fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
|
||||||
accessTokenInterceptor: AccessTokenInterceptor): OkHttpClient {
|
@Authenticated accessTokenProvider: AccessTokenProvider): OkHttpClient {
|
||||||
return okHttpClient.newBuilder()
|
return okHttpClient.addAccessTokenInterceptor(accessTokenProvider)
|
||||||
.apply {
|
|
||||||
// Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor
|
|
||||||
val existingCurlInterceptors = interceptors().filterIsInstance<CurlLoggingInterceptor>()
|
|
||||||
interceptors().removeAll(existingCurlInterceptors)
|
|
||||||
|
|
||||||
addInterceptor(accessTokenInterceptor)
|
|
||||||
|
|
||||||
// Re add eventually the curl logging interceptors
|
|
||||||
existingCurlInterceptors.forEach {
|
|
||||||
addInterceptor(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -233,6 +221,10 @@ internal abstract class SessionModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Authenticated
|
||||||
|
abstract fun bindAccessTokenProvider(provider: HomeserverAccessTokenProvider): AccessTokenProvider
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSession(session: DefaultSession): Session
|
abstract fun bindSession(session: DefaultSession): Session
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,10 @@ package im.vector.matrix.android.internal.session.account
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.cleanup.CleanupSession
|
import im.vector.matrix.android.internal.session.cleanup.CleanupSession
|
||||||
|
import im.vector.matrix.android.internal.session.identity.IdentityDisconnectTask
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> {
|
internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> {
|
||||||
|
@ -34,6 +36,7 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
|
||||||
private val accountAPI: AccountAPI,
|
private val accountAPI: AccountAPI,
|
||||||
private val eventBus: EventBus,
|
private val eventBus: EventBus,
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
|
private val identityDisconnectTask: IdentityDisconnectTask,
|
||||||
private val cleanupSession: CleanupSession
|
private val cleanupSession: CleanupSession
|
||||||
) : DeactivateAccountTask {
|
) : DeactivateAccountTask {
|
||||||
|
|
||||||
|
@ -44,6 +47,10 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
|
||||||
apiCall = accountAPI.deactivate(deactivateAccountParams)
|
apiCall = accountAPI.deactivate(deactivateAccountParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logout from identity server if any, ignoring errors
|
||||||
|
runCatching { identityDisconnectTask.execute(Unit) }
|
||||||
|
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
|
||||||
|
|
||||||
cleanupSession.handle()
|
cleanupSession.handle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.homeserver
|
package im.vector.matrix.android.internal.session.homeserver
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.Versions
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
@ -38,5 +39,11 @@ internal interface CapabilitiesAPI {
|
||||||
* Request the versions
|
* Request the versions
|
||||||
*/
|
*/
|
||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions")
|
||||||
fun getVersions(): Call<Unit>
|
fun getVersions(): Call<Versions>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ping the homeserver. We do not care about the returned data, so there is no use to parse them
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions")
|
||||||
|
fun ping(): Call<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,14 @@
|
||||||
package im.vector.matrix.android.internal.session.homeserver
|
package im.vector.matrix.android.internal.session.homeserver
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.auth.data.Versions
|
||||||
|
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
|
||||||
|
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||||
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
|
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
|
||||||
|
import im.vector.matrix.android.internal.wellknown.GetWellknownTask
|
||||||
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity
|
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntity
|
||||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||||
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||||
|
@ -32,7 +37,10 @@ internal interface GetHomeServerCapabilitiesTask : Task<Unit, Unit>
|
||||||
internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
||||||
private val capabilitiesAPI: CapabilitiesAPI,
|
private val capabilitiesAPI: CapabilitiesAPI,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val eventBus: EventBus
|
private val eventBus: EventBus,
|
||||||
|
private val getWellknownTask: GetWellknownTask,
|
||||||
|
@UserId
|
||||||
|
private val userId: String
|
||||||
) : GetHomeServerCapabilitiesTask {
|
) : GetHomeServerCapabilitiesTask {
|
||||||
|
|
||||||
override suspend fun execute(params: Unit) {
|
override suspend fun execute(params: Unit) {
|
||||||
|
@ -47,29 +55,54 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val uploadCapabilities = executeRequest<GetUploadCapabilitiesResult>(eventBus) {
|
|
||||||
apiCall = capabilitiesAPI.getUploadCapabilities()
|
|
||||||
}
|
|
||||||
|
|
||||||
val capabilities = runCatching {
|
val capabilities = runCatching {
|
||||||
executeRequest<GetCapabilitiesResult>(eventBus) {
|
executeRequest<GetCapabilitiesResult>(eventBus) {
|
||||||
apiCall = capabilitiesAPI.getCapabilities()
|
apiCall = capabilitiesAPI.getCapabilities()
|
||||||
}
|
}
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|
||||||
// TODO Add other call here (get version, etc.)
|
val uploadCapabilities = runCatching {
|
||||||
|
executeRequest<GetUploadCapabilitiesResult>(eventBus) {
|
||||||
|
apiCall = capabilitiesAPI.getUploadCapabilities()
|
||||||
|
}
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
insertInDb(capabilities, uploadCapabilities)
|
val versions = runCatching {
|
||||||
|
executeRequest<Versions>(null) {
|
||||||
|
apiCall = capabilitiesAPI.getVersions()
|
||||||
|
}
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
val wellknownResult = runCatching {
|
||||||
|
getWellknownTask.execute(GetWellknownTask.Params(userId))
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
insertInDb(capabilities, uploadCapabilities, versions, wellknownResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun insertInDb(getCapabilitiesResult: GetCapabilitiesResult?, getUploadCapabilitiesResult: GetUploadCapabilitiesResult) {
|
private suspend fun insertInDb(getCapabilitiesResult: GetCapabilitiesResult?,
|
||||||
|
getUploadCapabilitiesResult: GetUploadCapabilitiesResult?,
|
||||||
|
getVersionResult: Versions?,
|
||||||
|
getWellknownResult: WellknownResult?) {
|
||||||
monarchy.awaitTransaction { realm ->
|
monarchy.awaitTransaction { realm ->
|
||||||
val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm)
|
val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm)
|
||||||
|
|
||||||
homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
|
if (getCapabilitiesResult != null) {
|
||||||
|
homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
|
||||||
|
}
|
||||||
|
|
||||||
homeServerCapabilitiesEntity.maxUploadFileSize = getUploadCapabilitiesResult.maxUploadSize
|
if (getUploadCapabilitiesResult != null) {
|
||||||
?: HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN
|
homeServerCapabilitiesEntity.maxUploadFileSize = getUploadCapabilitiesResult.maxUploadSize
|
||||||
|
?: HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getVersionResult != null) {
|
||||||
|
homeServerCapabilitiesEntity.lastVersionIdentityServerSupported = getVersionResult.isLoginAndRegistrationSupportedBySdk()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
|
||||||
|
homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl
|
||||||
|
}
|
||||||
|
|
||||||
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
|
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,6 @@ internal data class ChangePassword(
|
||||||
)
|
)
|
||||||
|
|
||||||
// The spec says: If not present, the client should assume that password changes are possible via the API
|
// The spec says: If not present, the client should assume that password changes are possible via the API
|
||||||
internal fun GetCapabilitiesResult?.canChangePassword(): Boolean {
|
internal fun GetCapabilitiesResult.canChangePassword(): Boolean {
|
||||||
return this?.capabilities?.changePassword?.enabled.orTrue()
|
return capabilities?.changePassword?.enabled.orTrue()
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,10 @@ import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.wellknown.WellknownModule
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
@Module
|
@Module(includes = [WellknownModule::class])
|
||||||
internal abstract class HomeServerCapabilitiesModule {
|
internal abstract class HomeServerCapabilitiesModule {
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
|
|
@ -35,7 +35,7 @@ internal class HomeServerPinger @Inject constructor(private val taskExecutor: Ta
|
||||||
suspend fun canReachHomeServer(): Boolean {
|
suspend fun canReachHomeServer(): Boolean {
|
||||||
return try {
|
return try {
|
||||||
executeRequest<Unit>(null) {
|
executeRequest<Unit>(null) {
|
||||||
apiCall = capabilitiesAPI.getVersions()
|
apiCall = capabilitiesAPI.ping()
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
|
|
|
@ -0,0 +1,341 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.LifecycleRegistry
|
||||||
|
import dagger.Lazy
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
|
import im.vector.matrix.android.api.extensions.tryThis
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
import im.vector.matrix.android.api.failure.MatrixError
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
|
import im.vector.matrix.android.api.session.identity.FoundThreePid
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityService
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityServiceListener
|
||||||
|
import im.vector.matrix.android.api.session.identity.SharedState
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.api.util.NoOpCancellable
|
||||||
|
import im.vector.matrix.android.internal.di.AuthenticatedIdentity
|
||||||
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.session.identity.data.IdentityStore
|
||||||
|
import im.vector.matrix.android.internal.session.identity.todelete.AccountDataDataSource
|
||||||
|
import im.vector.matrix.android.internal.session.identity.todelete.observeNotNull
|
||||||
|
import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
|
||||||
|
import im.vector.matrix.android.internal.session.profile.BindThreePidsTask
|
||||||
|
import im.vector.matrix.android.internal.session.profile.UnbindThreePidsTask
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||||
|
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import im.vector.matrix.android.internal.util.ensureProtocol
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
|
@SessionScope
|
||||||
|
internal class DefaultIdentityService @Inject constructor(
|
||||||
|
private val identityStore: IdentityStore,
|
||||||
|
private val getOpenIdTokenTask: GetOpenIdTokenTask,
|
||||||
|
private val identityBulkLookupTask: IdentityBulkLookupTask,
|
||||||
|
private val identityRegisterTask: IdentityRegisterTask,
|
||||||
|
private val identityPingTask: IdentityPingTask,
|
||||||
|
private val identityDisconnectTask: IdentityDisconnectTask,
|
||||||
|
private val identityRequestTokenForBindingTask: IdentityRequestTokenForBindingTask,
|
||||||
|
@Unauthenticated
|
||||||
|
private val unauthenticatedOkHttpClient: Lazy<OkHttpClient>,
|
||||||
|
@AuthenticatedIdentity
|
||||||
|
private val okHttpClient: Lazy<OkHttpClient>,
|
||||||
|
private val retrofitFactory: RetrofitFactory,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
|
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
|
||||||
|
private val bindThreePidsTask: BindThreePidsTask,
|
||||||
|
private val submitTokenForBindingTask: IdentitySubmitTokenForBindingTask,
|
||||||
|
private val unbindThreePidsTask: UnbindThreePidsTask,
|
||||||
|
private val identityApiProvider: IdentityApiProvider,
|
||||||
|
private val accountDataDataSource: AccountDataDataSource,
|
||||||
|
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
|
||||||
|
private val sessionParams: SessionParams,
|
||||||
|
private val taskExecutor: TaskExecutor
|
||||||
|
) : IdentityService {
|
||||||
|
|
||||||
|
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
|
||||||
|
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
|
||||||
|
|
||||||
|
private val listeners = mutableSetOf<IdentityServiceListener>()
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||||
|
// Observe the account data change
|
||||||
|
accountDataDataSource
|
||||||
|
.getLiveAccountDataEvent(UserAccountData.TYPE_IDENTITY_SERVER)
|
||||||
|
.observeNotNull(lifecycleOwner) {
|
||||||
|
notifyIdentityServerUrlChange(it.getOrNull()?.content?.toModel<IdentityServerContent>()?.baseUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init identityApi
|
||||||
|
updateIdentityAPI(identityStore.getIdentityData()?.identityServerUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyIdentityServerUrlChange(baseUrl: String?) {
|
||||||
|
// This is maybe not a real change (echo of account data we are just setting)
|
||||||
|
if (identityStore.getIdentityData()?.identityServerUrl == baseUrl) {
|
||||||
|
Timber.d("Echo of local identity server url change, or no change")
|
||||||
|
} else {
|
||||||
|
// Url has changed, we have to reset our store, update internal configuration and notify listeners
|
||||||
|
identityStore.setUrl(baseUrl)
|
||||||
|
updateIdentityAPI(baseUrl)
|
||||||
|
listeners.toList().forEach { tryThis { it.onIdentityServerChange() } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First return the identity server provided during login phase.
|
||||||
|
* If null, provide the one in wellknown configuration of the homeserver
|
||||||
|
* Else return null
|
||||||
|
*/
|
||||||
|
override fun getDefaultIdentityServer(): String? {
|
||||||
|
return sessionParams.defaultIdentityServerUrl
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
?: homeServerCapabilitiesService.getHomeServerCapabilities().defaultIdentityServerUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCurrentIdentityServerUrl(): String? {
|
||||||
|
return identityStore.getIdentityData()?.identityServerUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) {
|
||||||
|
callback.onFailure(IdentityServiceError.OutdatedHomeServer)
|
||||||
|
return NoOpCancellable
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
identityStore.deletePendingBinding(threePid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) {
|
||||||
|
callback.onFailure(IdentityServiceError.OutdatedHomeServer)
|
||||||
|
return NoOpCancellable
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
bindThreePidsTask.execute(BindThreePidsTask.Params(threePid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
submitTokenForBindingTask.execute(IdentitySubmitTokenForBindingTask.Params(threePid, code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) {
|
||||||
|
callback.onFailure(IdentityServiceError.OutdatedHomeServer)
|
||||||
|
return NoOpCancellable
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
unbindThreePidsTask.execute(UnbindThreePidsTask.Params(threePid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
|
||||||
|
|
||||||
|
identityPingTask.execute(IdentityPingTask.Params(api))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disconnect(callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
identityDisconnectTask.execute(Unit)
|
||||||
|
|
||||||
|
identityStore.setUrl(null)
|
||||||
|
updateIdentityAPI(null)
|
||||||
|
updateAccountData(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setNewIdentityServer(url: String, callback: MatrixCallback<String>): Cancelable {
|
||||||
|
val urlCandidate = url.ensureProtocol()
|
||||||
|
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
val current = getCurrentIdentityServerUrl()
|
||||||
|
if (urlCandidate == current) {
|
||||||
|
// Nothing to do
|
||||||
|
Timber.d("Same URL, nothing to do")
|
||||||
|
} else {
|
||||||
|
// Disconnect previous one if any, first, because the token will change.
|
||||||
|
// In case of error when configuring the new identity server, this is not a big deal,
|
||||||
|
// we will ask for a new token on the previous Identity server
|
||||||
|
runCatching { identityDisconnectTask.execute(Unit) }
|
||||||
|
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
|
||||||
|
|
||||||
|
// Try to get a token
|
||||||
|
val token = getNewIdentityServerToken(urlCandidate)
|
||||||
|
|
||||||
|
identityStore.setUrl(urlCandidate)
|
||||||
|
identityStore.setToken(token)
|
||||||
|
updateIdentityAPI(urlCandidate)
|
||||||
|
|
||||||
|
updateAccountData(urlCandidate)
|
||||||
|
}
|
||||||
|
urlCandidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateAccountData(url: String?) {
|
||||||
|
// Also notify the listener
|
||||||
|
withContext(coroutineDispatchers.main) {
|
||||||
|
listeners.toList().forEach { tryThis { it.onIdentityServerChange() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.IdentityParams(
|
||||||
|
identityContent = IdentityServerContent(baseUrl = url)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable {
|
||||||
|
if (threePids.isEmpty()) {
|
||||||
|
callback.onSuccess(emptyList())
|
||||||
|
return NoOpCancellable
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
lookUpInternal(true, threePids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable {
|
||||||
|
if (threePids.isEmpty()) {
|
||||||
|
callback.onSuccess(emptyMap())
|
||||||
|
return NoOpCancellable
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
val lookupResult = lookUpInternal(true, threePids)
|
||||||
|
|
||||||
|
threePids.associateWith { threePid ->
|
||||||
|
// If not in lookup result, check if there is a pending binding
|
||||||
|
if (lookupResult.firstOrNull { it.threePid == threePid } == null) {
|
||||||
|
if (identityStore.getPendingBinding(threePid) == null) {
|
||||||
|
SharedState.NOT_SHARED
|
||||||
|
} else {
|
||||||
|
SharedState.BINDING_IN_PROGRESS
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SharedState.SHARED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun lookUpInternal(canRetry: Boolean, threePids: List<ThreePid>): List<FoundThreePid> {
|
||||||
|
ensureToken()
|
||||||
|
|
||||||
|
return try {
|
||||||
|
identityBulkLookupTask.execute(IdentityBulkLookupTask.Params(threePids))
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
// Refresh token?
|
||||||
|
when {
|
||||||
|
throwable.isInvalidToken() && canRetry -> {
|
||||||
|
identityStore.setToken(null)
|
||||||
|
lookUpInternal(false, threePids)
|
||||||
|
}
|
||||||
|
throwable.isTermsNotSigned() -> throw IdentityServiceError.TermsNotSignedException
|
||||||
|
else -> throw throwable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun ensureToken() {
|
||||||
|
val identityData = identityStore.getIdentityData() ?: throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
|
val url = identityData.identityServerUrl ?: throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
|
|
||||||
|
if (identityData.token == null) {
|
||||||
|
// Try to get a token
|
||||||
|
val token = getNewIdentityServerToken(url)
|
||||||
|
identityStore.setToken(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getNewIdentityServerToken(url: String): String {
|
||||||
|
val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
|
||||||
|
|
||||||
|
val openIdToken = getOpenIdTokenTask.execute(Unit)
|
||||||
|
val token = identityRegisterTask.execute(IdentityRegisterTask.Params(api, openIdToken))
|
||||||
|
|
||||||
|
return token.token
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addListener(listener: IdentityServiceListener) {
|
||||||
|
listeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeListener(listener: IdentityServiceListener) {
|
||||||
|
listeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateIdentityAPI(url: String?) {
|
||||||
|
identityApiProvider.identityApi = url
|
||||||
|
?.let { retrofitFactory.create(okHttpClient, it) }
|
||||||
|
?.create(IdentityAPI::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Throwable.isInvalidToken(): Boolean {
|
||||||
|
return this is Failure.ServerError
|
||||||
|
&& httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Throwable.isTermsNotSigned(): Boolean {
|
||||||
|
return this is Failure.ServerError
|
||||||
|
&& httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */
|
||||||
|
&& error.code == MatrixError.M_TERMS_NOT_SIGNED
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.auth.registration.SuccessResult
|
||||||
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityAccountResponse
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpParams
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpResponse
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestOwnershipParams
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForEmailBody
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForMsisdnBody
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenResponse
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/identity_service/latest
|
||||||
|
* This contain the requests which need an identity server token
|
||||||
|
*/
|
||||||
|
internal interface IdentityAPI {
|
||||||
|
/**
|
||||||
|
* Gets information about what user owns the access token used in the request.
|
||||||
|
* Will return a 403 for when terms are not signed
|
||||||
|
* Ref: https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2-account
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_IDENTITY_PATH_V2 + "account")
|
||||||
|
fun getAccount(): Call<IdentityAccountResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs out the access token, preventing it from being used to authenticate future requests to the server.
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/logout")
|
||||||
|
fun logout(): Call<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the hash detail to request a bunch of 3PIDs
|
||||||
|
* Ref: https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2-hash-details
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_IDENTITY_PATH_V2 + "hash_details")
|
||||||
|
fun hashDetails(): Call<IdentityHashDetailResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a bunch of 3PIDs
|
||||||
|
* Ref: https://matrix.org/docs/spec/identity_service/latest#post-matrix-identity-v2-lookup
|
||||||
|
*
|
||||||
|
* @param body the body request
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "lookup")
|
||||||
|
fun lookup(@Body body: IdentityLookUpParams): Call<IdentityLookUpResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a session to change the bind status of an email to an identity server
|
||||||
|
* The identity server will also send an email
|
||||||
|
*
|
||||||
|
* @param body
|
||||||
|
* @return the sid
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/email/requestToken")
|
||||||
|
fun requestTokenToBindEmail(@Body body: IdentityRequestTokenForEmailBody): Call<IdentityRequestTokenResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a session to change the bind status of an phone number to an identity server
|
||||||
|
* The identity server will also send an SMS on the ThreePid provided
|
||||||
|
*
|
||||||
|
* @param body
|
||||||
|
* @return the sid
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/msisdn/requestToken")
|
||||||
|
fun requestTokenToBindMsisdn(@Body body: IdentityRequestTokenForMsisdnBody): Call<IdentityRequestTokenResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate ownership of an email address, or a phone number.
|
||||||
|
* Ref:
|
||||||
|
* - https://matrix.org/docs/spec/identity_service/latest#post-matrix-identity-v2-validate-msisdn-submittoken
|
||||||
|
* - https://matrix.org/docs/spec/identity_service/latest#post-matrix-identity-v2-validate-email-submittoken
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken")
|
||||||
|
fun submitToken(@Path("medium") medium: String, @Body body: IdentityRequestOwnershipParams): Call<SuccessResult>
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
||||||
|
import im.vector.matrix.android.internal.session.identity.data.IdentityStore
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class IdentityAccessTokenProvider @Inject constructor(
|
||||||
|
private val identityStore: IdentityStore
|
||||||
|
) : AccessTokenProvider {
|
||||||
|
override fun getToken() = identityStore.getIdentityData()?.token
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@ -14,9 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotx.features.createdirect
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
import im.vector.riotx.core.platform.VectorSharedActionViewModel
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CreateDirectRoomSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<CreateDirectRoomSharedAction>()
|
@SessionScope
|
||||||
|
internal class IdentityApiProvider @Inject constructor() {
|
||||||
|
|
||||||
|
var identityApi: IdentityAPI? = null
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRegisterResponse
|
||||||
|
import im.vector.matrix.android.internal.session.openid.RequestOpenIdTokenResponse
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/identity_service/latest
|
||||||
|
* This contain the requests which do not need an identity server token
|
||||||
|
*/
|
||||||
|
internal interface IdentityAuthAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||||
|
* Simple ping call to check if server exists and is alive
|
||||||
|
*
|
||||||
|
* Ref: https://matrix.org/docs/spec/identity_service/unstable#status-check
|
||||||
|
* https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2
|
||||||
|
*
|
||||||
|
* @return 200 in case of success
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_IDENTITY_PREFIX_PATH)
|
||||||
|
fun ping(): Call<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ping v1 will be used to check outdated Identity server
|
||||||
|
*/
|
||||||
|
@GET("_matrix/identity/api/v1")
|
||||||
|
fun pingV1(): Call<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchanges an OpenID token from the homeserver for an access token to access the identity server.
|
||||||
|
* The request body is the same as the values returned by /openid/request_token in the Client-Server API.
|
||||||
|
*/
|
||||||
|
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/register")
|
||||||
|
fun register(@Body openIdToken: RequestOpenIdTokenResponse): Call<IdentityRegisterResponse>
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
import im.vector.matrix.android.api.failure.MatrixError
|
||||||
|
import im.vector.matrix.android.api.session.identity.FoundThreePid
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
|
import im.vector.matrix.android.api.session.identity.toMedium
|
||||||
|
import im.vector.matrix.android.internal.crypto.attachments.MXEncryptedAttachments.base64ToBase64Url
|
||||||
|
import im.vector.matrix.android.internal.crypto.tools.withOlmUtility
|
||||||
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.identity.data.IdentityStore
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpParams
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityLookUpResponse
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface IdentityBulkLookupTask : Task<IdentityBulkLookupTask.Params, List<FoundThreePid>> {
|
||||||
|
data class Params(
|
||||||
|
val threePids: List<ThreePid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultIdentityBulkLookupTask @Inject constructor(
|
||||||
|
private val identityApiProvider: IdentityApiProvider,
|
||||||
|
private val identityStore: IdentityStore,
|
||||||
|
@UserId private val userId: String
|
||||||
|
) : IdentityBulkLookupTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: IdentityBulkLookupTask.Params): List<FoundThreePid> {
|
||||||
|
val identityAPI = getIdentityApiAndEnsureTerms(identityApiProvider, userId)
|
||||||
|
val identityData = identityStore.getIdentityData() ?: throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
|
val pepper = identityData.hashLookupPepper
|
||||||
|
val hashDetailResponse = if (pepper == null) {
|
||||||
|
// We need to fetch the hash details first
|
||||||
|
fetchAndStoreHashDetails(identityAPI)
|
||||||
|
} else {
|
||||||
|
IdentityHashDetailResponse(pepper, identityData.hashLookupAlgorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hashDetailResponse.algorithms.contains("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
|
||||||
|
// Also, what we have in cache could be outdated, the identity server maybe now supports sha256
|
||||||
|
throw IdentityServiceError.BulkLookupSha256NotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
val hashedAddresses = withOlmUtility { olmUtility ->
|
||||||
|
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>
|
||||||
|
return handleSuccess(params.threePids, hashedAddresses, identityLookUpV2Response)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun lookUpInternal(identityAPI: IdentityAPI,
|
||||||
|
hashedAddresses: List<String>,
|
||||||
|
hashDetailResponse: IdentityHashDetailResponse,
|
||||||
|
canRetry: Boolean): IdentityLookUpResponse {
|
||||||
|
return try {
|
||||||
|
executeRequest(null) {
|
||||||
|
apiCall = identityAPI.lookup(IdentityLookUpParams(
|
||||||
|
hashedAddresses,
|
||||||
|
IdentityHashDetailResponse.ALGORITHM_SHA256,
|
||||||
|
hashDetailResponse.pepper
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
// Catch invalid hash pepper and retry
|
||||||
|
if (canRetry && failure is Failure.ServerError && failure.error.code == MatrixError.M_INVALID_PEPPER) {
|
||||||
|
// This is not documented, by the error can contain the new pepper!
|
||||||
|
if (!failure.error.newLookupPepper.isNullOrEmpty()) {
|
||||||
|
// Store it and use it right now
|
||||||
|
hashDetailResponse.copy(pepper = failure.error.newLookupPepper)
|
||||||
|
.also { identityStore.setHashDetails(it) }
|
||||||
|
.let { lookUpInternal(identityAPI, hashedAddresses, it, false /* Avoid infinite loop */) }
|
||||||
|
} else {
|
||||||
|
// Retrieve the new hash details
|
||||||
|
val newHashDetailResponse = fetchAndStoreHashDetails(identityAPI)
|
||||||
|
|
||||||
|
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
|
||||||
|
// Also, what we have in cache is maybe outdated, the identity server maybe now support sha256
|
||||||
|
throw IdentityServiceError.BulkLookupSha256NotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
lookUpInternal(identityAPI, hashedAddresses, newHashDetailResponse, false /* Avoid infinite loop */)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Other error
|
||||||
|
throw failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchAndStoreHashDetails(identityAPI: IdentityAPI): IdentityHashDetailResponse {
|
||||||
|
return executeRequest<IdentityHashDetailResponse>(null) {
|
||||||
|
apiCall = identityAPI.hashDetails()
|
||||||
|
}
|
||||||
|
.also { identityStore.setHashDetails(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSuccess(threePids: List<ThreePid>, hashedAddresses: List<String>, identityLookUpResponse: IdentityLookUpResponse): List<FoundThreePid> {
|
||||||
|
return identityLookUpResponse.mappings.keys.map { hashedAddress ->
|
||||||
|
FoundThreePid(threePids[hashedAddresses.indexOf(hashedAddress)], identityLookUpResponse.mappings[hashedAddress] ?: error(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
|
import im.vector.matrix.android.internal.di.AuthenticatedIdentity
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface IdentityDisconnectTask : Task<Unit, Unit>
|
||||||
|
|
||||||
|
internal class DefaultIdentityDisconnectTask @Inject constructor(
|
||||||
|
private val identityApiProvider: IdentityApiProvider,
|
||||||
|
@AuthenticatedIdentity
|
||||||
|
private val accessTokenProvider: AccessTokenProvider
|
||||||
|
) : IdentityDisconnectTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: Unit) {
|
||||||
|
val identityAPI = identityApiProvider.identityApi ?: throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
|
|
||||||
|
// Ensure we have a token.
|
||||||
|
// We can have an identity server configured, but no token yet.
|
||||||
|
if (accessTokenProvider.getToken() == null) {
|
||||||
|
Timber.d("No token to disconnect identity server.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
executeRequest<Unit>(null) {
|
||||||
|
apiCall = identityAPI.logout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||||
|
import im.vector.matrix.android.internal.di.AuthenticatedIdentity
|
||||||
|
import im.vector.matrix.android.internal.di.IdentityDatabase
|
||||||
|
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
||||||
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
|
import im.vector.matrix.android.internal.di.UserMd5
|
||||||
|
import im.vector.matrix.android.internal.network.httpclient.addAccessTokenInterceptor
|
||||||
|
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
||||||
|
import im.vector.matrix.android.internal.session.SessionModule
|
||||||
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.session.identity.data.IdentityStore
|
||||||
|
import im.vector.matrix.android.internal.session.identity.db.IdentityRealmModule
|
||||||
|
import im.vector.matrix.android.internal.session.identity.db.RealmIdentityStore
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@Module
|
||||||
|
internal abstract class IdentityModule {
|
||||||
|
|
||||||
|
@Module
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@SessionScope
|
||||||
|
@AuthenticatedIdentity
|
||||||
|
fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
|
||||||
|
@AuthenticatedIdentity accessTokenProvider: AccessTokenProvider): OkHttpClient {
|
||||||
|
return okHttpClient.addAccessTokenInterceptor(accessTokenProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@IdentityDatabase
|
||||||
|
@SessionScope
|
||||||
|
fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
|
||||||
|
@SessionFilesDirectory directory: File,
|
||||||
|
@UserMd5 userMd5: String): RealmConfiguration {
|
||||||
|
return RealmConfiguration.Builder()
|
||||||
|
.directory(directory)
|
||||||
|
.name("matrix-sdk-identity.realm")
|
||||||
|
.apply {
|
||||||
|
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||||
|
}
|
||||||
|
.modules(IdentityRealmModule())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@AuthenticatedIdentity
|
||||||
|
abstract fun bindAccessTokenProvider(provider: IdentityAccessTokenProvider): AccessTokenProvider
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindIdentityStore(store: RealmIdentityStore): IdentityStore
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindIdentityPingTask(task: DefaultIdentityPingTask): IdentityPingTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindIdentityRegisterTask(task: DefaultIdentityRegisterTask): IdentityRegisterTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindIdentityRequestTokenForBindingTask(task: DefaultIdentityRequestTokenForBindingTask): IdentityRequestTokenForBindingTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindIdentitySubmitTokenForBindingTask(task: DefaultIdentitySubmitTokenForBindingTask): IdentitySubmitTokenForBindingTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindIdentityBulkLookupTask(task: DefaultIdentityBulkLookupTask): IdentityBulkLookupTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindIdentityDisconnectTask(task: DefaultIdentityDisconnectTask): IdentityDisconnectTask
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
|
internal interface IdentityPingTask : Task<IdentityPingTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val identityAuthAPI: IdentityAuthAPI
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultIdentityPingTask @Inject constructor() : IdentityPingTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: IdentityPingTask.Params) {
|
||||||
|
try {
|
||||||
|
executeRequest<Unit>(null) {
|
||||||
|
apiCall = params.identityAuthAPI.ping()
|
||||||
|
}
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
if (throwable is Failure.ServerError && throwable.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
|
||||||
|
// Check if API v1 is available
|
||||||
|
executeRequest<Unit>(null) {
|
||||||
|
apiCall = params.identityAuthAPI.pingV1()
|
||||||
|
}
|
||||||
|
// API V1 is responding, but not V2 -> Outdated
|
||||||
|
throw IdentityServiceError.OutdatedIdentityServer
|
||||||
|
} else {
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRegisterResponse
|
||||||
|
import im.vector.matrix.android.internal.session.openid.RequestOpenIdTokenResponse
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface IdentityRegisterTask : Task<IdentityRegisterTask.Params, IdentityRegisterResponse> {
|
||||||
|
data class Params(
|
||||||
|
val identityAuthAPI: IdentityAuthAPI,
|
||||||
|
val openIdTokenResponse: RequestOpenIdTokenResponse
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultIdentityRegisterTask @Inject constructor() : IdentityRegisterTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: IdentityRegisterTask.Params): IdentityRegisterResponse {
|
||||||
|
return executeRequest(null) {
|
||||||
|
apiCall = params.identityAuthAPI.register(params.openIdTokenResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
|
import im.vector.matrix.android.api.session.identity.getCountryCode
|
||||||
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.identity.data.IdentityPendingBinding
|
||||||
|
import im.vector.matrix.android.internal.session.identity.data.IdentityStore
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForEmailBody
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenForMsisdnBody
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestTokenResponse
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface IdentityRequestTokenForBindingTask : Task<IdentityRequestTokenForBindingTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val threePid: ThreePid,
|
||||||
|
// True to request the identity server to send again the email or the SMS
|
||||||
|
val sendAgain: Boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultIdentityRequestTokenForBindingTask @Inject constructor(
|
||||||
|
private val identityApiProvider: IdentityApiProvider,
|
||||||
|
private val identityStore: IdentityStore,
|
||||||
|
@UserId private val userId: String
|
||||||
|
) : IdentityRequestTokenForBindingTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: IdentityRequestTokenForBindingTask.Params) {
|
||||||
|
val identityAPI = getIdentityApiAndEnsureTerms(identityApiProvider, userId)
|
||||||
|
|
||||||
|
val identityPendingBinding = identityStore.getPendingBinding(params.threePid)
|
||||||
|
|
||||||
|
if (params.sendAgain && identityPendingBinding == null) {
|
||||||
|
throw IdentityServiceError.NoCurrentBindingError
|
||||||
|
}
|
||||||
|
|
||||||
|
val clientSecret = identityPendingBinding?.clientSecret ?: UUID.randomUUID().toString()
|
||||||
|
val sendAttempt = identityPendingBinding?.sendAttempt?.inc() ?: 1
|
||||||
|
|
||||||
|
val tokenResponse = executeRequest<IdentityRequestTokenResponse>(null) {
|
||||||
|
apiCall = when (params.threePid) {
|
||||||
|
is ThreePid.Email -> identityAPI.requestTokenToBindEmail(IdentityRequestTokenForEmailBody(
|
||||||
|
clientSecret = clientSecret,
|
||||||
|
sendAttempt = sendAttempt,
|
||||||
|
email = params.threePid.email
|
||||||
|
))
|
||||||
|
is ThreePid.Msisdn -> {
|
||||||
|
identityAPI.requestTokenToBindMsisdn(IdentityRequestTokenForMsisdnBody(
|
||||||
|
clientSecret = clientSecret,
|
||||||
|
sendAttempt = sendAttempt,
|
||||||
|
phoneNumber = params.threePid.msisdn,
|
||||||
|
countryCode = params.threePid.getCountryCode()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store client secret, send attempt and sid
|
||||||
|
identityStore.storePendingBinding(
|
||||||
|
params.threePid,
|
||||||
|
IdentityPendingBinding(
|
||||||
|
clientSecret = clientSecret,
|
||||||
|
sendAttempt = sendAttempt,
|
||||||
|
sid = tokenResponse.sid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
|
import im.vector.matrix.android.api.session.identity.toMedium
|
||||||
|
import im.vector.matrix.android.internal.auth.registration.SuccessResult
|
||||||
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.identity.data.IdentityStore
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityRequestOwnershipParams
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface IdentitySubmitTokenForBindingTask : Task<IdentitySubmitTokenForBindingTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val threePid: ThreePid,
|
||||||
|
val token: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultIdentitySubmitTokenForBindingTask @Inject constructor(
|
||||||
|
private val identityApiProvider: IdentityApiProvider,
|
||||||
|
private val identityStore: IdentityStore,
|
||||||
|
@UserId private val userId: String
|
||||||
|
) : IdentitySubmitTokenForBindingTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: IdentitySubmitTokenForBindingTask.Params) {
|
||||||
|
val identityAPI = getIdentityApiAndEnsureTerms(identityApiProvider, userId)
|
||||||
|
val identityPendingBinding = identityStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError
|
||||||
|
|
||||||
|
val tokenResponse = executeRequest<SuccessResult>(null) {
|
||||||
|
apiCall = identityAPI.submitToken(
|
||||||
|
params.threePid.toMedium(),
|
||||||
|
IdentityRequestOwnershipParams(
|
||||||
|
clientSecret = identityPendingBinding.clientSecret,
|
||||||
|
sid = identityPendingBinding.sid,
|
||||||
|
token = params.token
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokenResponse.isSuccess()) {
|
||||||
|
throw IdentityServiceError.BindingError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.identity.IdentityServiceError
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityAccountResponse
|
||||||
|
|
||||||
|
internal suspend fun getIdentityApiAndEnsureTerms(identityApiProvider: IdentityApiProvider, userId: String): IdentityAPI {
|
||||||
|
val identityAPI = identityApiProvider.identityApi ?: throw IdentityServiceError.NoIdentityServerConfigured
|
||||||
|
|
||||||
|
// Always check that we have access to the service (regarding terms)
|
||||||
|
val identityAccountResponse = executeRequest<IdentityAccountResponse>(null) {
|
||||||
|
apiCall = identityAPI.getAccount()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(userId == identityAccountResponse.userId)
|
||||||
|
|
||||||
|
return identityAPI
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity.data
|
||||||
|
|
||||||
|
internal data class IdentityData(
|
||||||
|
val identityServerUrl: String?,
|
||||||
|
val token: String?,
|
||||||
|
val hashLookupPepper: String?,
|
||||||
|
val hashLookupAlgorithm: List<String>
|
||||||
|
)
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity.data
|
||||||
|
|
||||||
|
internal data class IdentityPendingBinding(
|
||||||
|
/* Managed by Riot */
|
||||||
|
val clientSecret: String,
|
||||||
|
/* Managed by Riot */
|
||||||
|
val sendAttempt: Int,
|
||||||
|
/* Provided by the identity server */
|
||||||
|
val sid: String
|
||||||
|
)
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.session.identity.data
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||||
|
import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
|
||||||
|
|
||||||
|
internal interface IdentityStore {
|
||||||
|
|
||||||
|
fun getIdentityData(): IdentityData?
|
||||||
|
|
||||||
|
fun setUrl(url: String?)
|
||||||
|
|
||||||
|
fun setToken(token: String?)
|
||||||
|
|
||||||
|
fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store details about a current binding
|
||||||
|
*/
|
||||||
|
fun storePendingBinding(threePid: ThreePid, data: IdentityPendingBinding)
|
||||||
|
|
||||||
|
fun getPendingBinding(threePid: ThreePid): IdentityPendingBinding?
|
||||||
|
|
||||||
|
fun deletePendingBinding(threePid: ThreePid)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun IdentityStore.getIdentityServerUrlWithoutProtocol(): String? {
|
||||||
|
return getIdentityData()?.identityServerUrl?.substringAfter("://")
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue