Merge branch 'develop' into feature/forward_pagination

This commit is contained in:
ganfra 2020-05-19 15:25:33 +02:00 committed by GitHub
commit e1c6542e03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
371 changed files with 12490 additions and 1618 deletions

View File

@ -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>

View File

@ -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)
=================================================== ===================================================

92
docs/identity_server.md Normal file
View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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'

View File

@ -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) {

View File

@ -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)

View File

@ -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>> {

View File

@ -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
} }
} }

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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
} }

View File

@ -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 {

View File

@ -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
)

View File

@ -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()
}

View File

@ -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

View File

@ -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
) )

View File

@ -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()
}

View File

@ -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"
} }

View File

@ -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.

View File

@ -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

View File

@ -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
)

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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>>
} }

View File

@ -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
*/ */

View File

@ -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>
)

View File

@ -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
}

View File

@ -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

View File

@ -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
) { ) {

View File

@ -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
} }

View File

@ -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)

View File

@ -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() }

View File

@ -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)

View File

@ -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)
} }
} }

View File

@ -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)
}
}

View File

@ -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)
} }

View File

@ -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

View File

@ -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()
}

View File

@ -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>> {

View File

@ -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)
} }

View File

@ -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)
} }
} }
} }

View File

@ -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('/', '_')

View File

@ -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!!)

View File

@ -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) {

View File

@ -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
}
}

View File

@ -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()

View File

@ -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)

View File

@ -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")
}
}
}
} }

View File

@ -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)
} }

View File

@ -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()
}
}

View File

@ -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

View File

@ -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")
} }

View File

@ -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)
} }
} }

View File

@ -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
) )
} }
} }

View File

@ -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() {

View File

@ -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,

View File

@ -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()

View File

@ -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

View File

@ -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":*}"""
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
} }

View File

@ -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/"
} }

View File

@ -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()
}

View File

@ -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?
}

View File

@ -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
}

View File

@ -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")

View File

@ -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}"
} }
} }

View File

@ -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,

View File

@ -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

View File

@ -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()
} }
} }

View File

@ -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>
} }

View File

@ -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
} }

View File

@ -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()
} }

View File

@ -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

View File

@ -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) {

View File

@ -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
}

View File

@ -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>
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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>
}

View File

@ -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(""))
}
}
}

View File

@ -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()
}
}
}

View File

@ -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
}

View File

@ -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
}
}
}
}

View File

@ -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)
}
}
}

View File

@ -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
)
)
}
}

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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>
)

View File

@ -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
)

View File

@ -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