Merge branch 'develop' into feature/integration_manager

This commit is contained in:
ganfra 2020-05-19 16:08:46 +02:00
commit dea903bcb5
291 changed files with 9915 additions and 1094 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,4 +1,30 @@
Changes in RiotX 0.20.0 (2020-XX-XX) Changes in RiotX 0.21.0 (2020-XX-XX)
===================================================
Features ✨:
- Identity server support (#607)
- Switch language support (#41)
Improvements 🙌:
- Better connectivity lost indicator when airplane mode is on
- Add a setting to hide redacted events (#951)
Bugfix 🐛:
- Fix issues with FontScale switch (#69, #645)
Translations 🗣:
-
SDK API changes ⚠️:
-
Build 🧱:
-
Other changes:
-
Changes in RiotX 0.20.0 (2020-05-15)
=================================================== ===================================================
Features ✨: Features ✨:
@ -13,18 +39,10 @@ Bugfix 🐛:
- Fix | Verify Manually by Text crashes if private SSK not known (#1337) - 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) - 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) - Random Crashes while doing sth with cross signing keys (#1364)
- Crash | crash while restoring key backup (#1366)
Translations 🗣:
-
SDK API changes ⚠️: SDK API changes ⚠️:
- excludedUserIds parameter add to to UserService.getPagedUsersLive() function - excludedUserIds parameter added to the UserService.getPagedUsersLive() function
Build 🧱:
-
Other changes:
-
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

@ -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
@ -94,6 +95,11 @@ class RxSession(private val session: Session) {
return session.getPagedUsersLive(filter, excludedUserIds).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 {
session.createRoom(roomParams, it) session.createRoom(roomParams, it)
} }

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

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

@ -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,7 +20,6 @@ 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.auth.wellknown.WellknownResult
@ -37,6 +36,11 @@ interface AuthenticationService {
*/ */
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,15 +78,6 @@ 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
*/ */

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

@ -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
import im.vector.matrix.android.api.session.widgets.WidgetService import im.vector.matrix.android.api.session.widgets.WidgetService
@ -55,6 +57,7 @@ interface Session :
SignOutService, SignOutService,
FilterService, FilterService,
FileService, FileService,
TermsService,
ProfileService, ProfileService,
PushRuleService, PushRuleService,
PushersService, PushersService,
@ -79,7 +82,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
@ -147,6 +150,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

@ -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,16 +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.wellknown.DefaultDirectLoginTask import im.vector.matrix.android.internal.auth.login.DefaultDirectLoginTask
import im.vector.matrix.android.internal.auth.wellknown.DefaultGetWellknownTask import im.vector.matrix.android.internal.auth.login.DirectLoginTask
import im.vector.matrix.android.internal.auth.wellknown.DirectLoginTask
import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask
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
@ -74,9 +73,6 @@ internal abstract class AuthModule {
@Binds @Binds
abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator
@Binds
abstract fun bindGetWellknownTask(task: DefaultGetWellknownTask): GetWellknownTask
@Binds @Binds
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
} }

View File

@ -23,7 +23,6 @@ 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
@ -33,14 +32,14 @@ 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.auth.wellknown.DirectLoginTask
import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask
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
@ -50,7 +49,7 @@ 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.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
@ -87,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 {
@ -246,7 +252,8 @@ internal class DefaultAuthenticationService @Inject constructor(
retrofitFactory, retrofitFactory,
coroutineDispatchers, coroutineDispatchers,
sessionCreator, sessionCreator,
pendingSessionStore pendingSessionStore,
taskExecutor.executorScope
).also { ).also {
currentRegistrationWizard = it currentRegistrationWizard = it
} }
@ -266,7 +273,8 @@ internal class DefaultAuthenticationService @Inject constructor(
retrofitFactory, retrofitFactory,
coroutineDispatchers, coroutineDispatchers,
sessionCreator, sessionCreator,
pendingSessionStore pendingSessionStore,
taskExecutor.executorScope
).also { ).also {
currentLoginWizard = it currentLoginWizard = it
} }
@ -283,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()
@ -300,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()
} }
} }
@ -308,7 +316,7 @@ 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)
} }
} }

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

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.auth.wellknown package im.vector.matrix.android.internal.auth.login
import dagger.Lazy import dagger.Lazy
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials

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

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

@ -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.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes) val liveEvents = ChunkEntity.findLastLiveChunkFromRoom(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

@ -28,8 +28,9 @@ internal object NetworkConstants {
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 // Identity server
const val URI_IDENTITY_PATH = "_matrix/identity/api/v1/" const val URI_IDENTITY_PREFIX_PATH = "_matrix/identity/v2"
const val URI_IDENTITY_PATH_V2 = "_matrix/identity/v2/" const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/"
const val URI_API_PREFIX_IDENTITY = "_matrix/identity/api/v1" // 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.api.session.widgets.WidgetService import im.vector.matrix.android.api.session.widgets.WidgetService
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
@ -51,14 +52,16 @@ 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.integrationmanager.IntegrationManager import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager
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.session.widgets.WidgetManager import im.vector.matrix.android.internal.session.widgets.WidgetManager
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
@ -85,6 +88,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>,
@ -103,8 +107,11 @@ internal class DefaultSession @Inject constructor(
private val timelineEventDecryptor: TimelineEventDecryptor, private val timelineEventDecryptor: TimelineEventDecryptor,
private val integrationManager: IntegrationManager, private val integrationManager: IntegrationManager,
private val widgetManager: WidgetManager, private val widgetManager: WidgetManager,
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(),
@ -114,6 +121,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(),
@ -142,6 +150,7 @@ internal class DefaultSession @Inject constructor(
shieldTrustUpdater.start() shieldTrustUpdater.start()
integrationManager.start() integrationManager.start()
widgetManager.start() widgetManager.start()
defaultIdentityService.start()
} }
override fun requireBackgroundSync() { override fun requireBackgroundSync() {
@ -186,6 +195,10 @@ internal class DefaultSession @Inject constructor(
shieldTrustUpdater.stop() shieldTrustUpdater.stop()
integrationManager.stop() integrationManager.stop()
widgetManager.stop() widgetManager.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> {
@ -215,7 +228,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)
} }
} }
@ -229,6 +242,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)
} }
@ -239,6 +254,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,7 @@ 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.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
@ -51,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.session.widgets.WidgetModule import im.vector.matrix.android.internal.session.widgets.WidgetModule
@ -74,6 +76,8 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
PushersModule::class, PushersModule::class,
OpenIdModule::class, OpenIdModule::class,
WidgetModule::class, WidgetModule::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

@ -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
import im.vector.matrix.android.internal.session.SessionScope
import javax.inject.Inject
@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("://")
}

View File

@ -0,0 +1,30 @@
/*
* 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.db
import io.realm.RealmList
import io.realm.RealmObject
internal open class IdentityDataEntity(
var identityServerUrl: String? = null,
var token: String? = null,
var hashLookupPepper: String? = null,
var hashLookupAlgorithm: RealmList<String> = RealmList()
) : RealmObject() {
companion object
}

View File

@ -0,0 +1,62 @@
/*
* 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.db
import io.realm.Realm
import io.realm.RealmList
import io.realm.kotlin.createObject
import io.realm.kotlin.where
/**
* Only one object can be stored at a time
*/
internal fun IdentityDataEntity.Companion.get(realm: Realm): IdentityDataEntity? {
return realm.where<IdentityDataEntity>().findFirst()
}
private fun IdentityDataEntity.Companion.getOrCreate(realm: Realm): IdentityDataEntity {
return get(realm) ?: realm.createObject()
}
internal fun IdentityDataEntity.Companion.setUrl(realm: Realm,
url: String?) {
realm.where<IdentityDataEntity>().findAll().deleteAllFromRealm()
// Delete all pending binding if any
IdentityPendingBindingEntity.deleteAll(realm)
if (url != null) {
getOrCreate(realm).apply {
identityServerUrl = url
}
}
}
internal fun IdentityDataEntity.Companion.setToken(realm: Realm,
newToken: String?) {
get(realm)?.apply {
token = newToken
}
}
internal fun IdentityDataEntity.Companion.setHashDetails(realm: Realm,
pepper: String,
algorithms: List<String>) {
get(realm)?.apply {
hashLookupPepper = pepper
hashLookupAlgorithm = RealmList<String>().apply { addAll(algorithms) }
}
}

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.internal.session.identity.db
import im.vector.matrix.android.internal.session.identity.data.IdentityData
import im.vector.matrix.android.internal.session.identity.data.IdentityPendingBinding
internal object IdentityMapper {
fun map(entity: IdentityDataEntity): IdentityData {
return IdentityData(
identityServerUrl = entity.identityServerUrl,
token = entity.token,
hashLookupPepper = entity.hashLookupPepper,
hashLookupAlgorithm = entity.hashLookupAlgorithm.toList()
)
}
fun map(entity: IdentityPendingBindingEntity): IdentityPendingBinding {
return IdentityPendingBinding(
clientSecret = entity.clientSecret,
sendAttempt = entity.sendAttempt,
sid = entity.sid
)
}
}

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.internal.session.identity.db
import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.android.api.session.identity.toMedium
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
internal open class IdentityPendingBindingEntity(
@PrimaryKey var threePid: String = "",
/* Managed by Riot */
var clientSecret: String = "",
/* Managed by Riot */
var sendAttempt: Int = 0,
/* Provided by the identity server */
var sid: String = ""
) : RealmObject() {
companion object {
fun ThreePid.toPrimaryKey() = "${toMedium()}_$value"
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.db
import im.vector.matrix.android.api.session.identity.ThreePid
import io.realm.Realm
import io.realm.kotlin.createObject
import io.realm.kotlin.where
internal fun IdentityPendingBindingEntity.Companion.get(realm: Realm, threePid: ThreePid): IdentityPendingBindingEntity? {
return realm.where<IdentityPendingBindingEntity>()
.equalTo(IdentityPendingBindingEntityFields.THREE_PID, threePid.toPrimaryKey())
.findFirst()
}
internal fun IdentityPendingBindingEntity.Companion.getOrCreate(realm: Realm, threePid: ThreePid): IdentityPendingBindingEntity {
return get(realm, threePid) ?: realm.createObject(threePid.toPrimaryKey())
}
internal fun IdentityPendingBindingEntity.Companion.delete(realm: Realm, threePid: ThreePid) {
get(realm, threePid)?.deleteFromRealm()
}
internal fun IdentityPendingBindingEntity.Companion.deleteAll(realm: Realm) {
realm.where<IdentityPendingBindingEntity>()
.findAll()
.deleteAllFromRealm()
}

View File

@ -0,0 +1,29 @@
/*
* 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.db
import io.realm.annotations.RealmModule
/**
* Realm module for identity server classes
*/
@RealmModule(library = true,
classes = [
IdentityDataEntity::class,
IdentityPendingBindingEntity::class
])
internal class IdentityRealmModule

View File

@ -0,0 +1,91 @@
/*
* 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.db
import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.android.internal.di.IdentityDatabase
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.identity.data.IdentityPendingBinding
import im.vector.matrix.android.internal.session.identity.data.IdentityData
import im.vector.matrix.android.internal.session.identity.data.IdentityStore
import im.vector.matrix.android.internal.session.identity.model.IdentityHashDetailResponse
import io.realm.Realm
import io.realm.RealmConfiguration
import javax.inject.Inject
@SessionScope
internal class RealmIdentityStore @Inject constructor(
@IdentityDatabase
private val realmConfiguration: RealmConfiguration
) : IdentityStore {
override fun getIdentityData(): IdentityData? {
return Realm.getInstance(realmConfiguration).use { realm ->
IdentityDataEntity.get(realm)?.let { IdentityMapper.map(it) }
}
}
override fun setUrl(url: String?) {
Realm.getInstance(realmConfiguration).use {
it.executeTransaction { realm ->
IdentityDataEntity.setUrl(realm, url)
}
}
}
override fun setToken(token: String?) {
Realm.getInstance(realmConfiguration).use {
it.executeTransaction { realm ->
IdentityDataEntity.setToken(realm, token)
}
}
}
override fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) {
Realm.getInstance(realmConfiguration).use {
it.executeTransaction { realm ->
IdentityDataEntity.setHashDetails(realm, hashDetailResponse.pepper, hashDetailResponse.algorithms)
}
}
}
override fun storePendingBinding(threePid: ThreePid, data: IdentityPendingBinding) {
Realm.getInstance(realmConfiguration).use {
it.executeTransaction { realm ->
IdentityPendingBindingEntity.getOrCreate(realm, threePid).let { entity ->
entity.clientSecret = data.clientSecret
entity.sendAttempt = data.sendAttempt
entity.sid = data.sid
}
}
}
}
override fun getPendingBinding(threePid: ThreePid): IdentityPendingBinding? {
return Realm.getInstance(realmConfiguration).use { realm ->
IdentityPendingBindingEntity.get(realm, threePid)?.let { IdentityMapper.map(it) }
}
}
override fun deletePendingBinding(threePid: ThreePid) {
Realm.getInstance(realmConfiguration).use {
it.executeTransaction { realm ->
IdentityPendingBindingEntity.delete(realm, threePid)
}
}
}
}

View File

@ -0,0 +1,29 @@
/*
* 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.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class IdentityAccountResponse(
/**
* Required. The user ID which registered the token.
*/
@Json(name = "user_id")
val userId: String
)

View File

@ -0,0 +1,45 @@
/*
* 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.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Ref: https://github.com/matrix-org/matrix-doc/blob/hs/hash-identity/proposals/2134-identity-hash-lookup.md
*/
@JsonClass(generateAdapter = true)
internal data class IdentityHashDetailResponse(
/**
* Required. The pepper the client MUST use in hashing identifiers, and MUST supply to the /lookup endpoint when performing lookups.
* Servers SHOULD rotate this string often.
*/
@Json(name = "lookup_pepper")
val pepper: String,
/**
* Required. The algorithms the server supports. Must contain at least "sha256".
* "none" can be another possible value.
*/
@Json(name = "algorithms")
val algorithms: List<String>
) {
companion object {
const val ALGORITHM_SHA256 = "sha256"
const val ALGORITHM_NONE = "none"
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Ref: https://github.com/matrix-org/matrix-doc/blob/hs/hash-identity/proposals/2134-identity-hash-lookup.md
*/
@JsonClass(generateAdapter = true)
internal data class IdentityLookUpParams(
/**
* Required. The addresses to look up. The format of the entries here depend on the algorithm used.
* Note that queries which have been incorrectly hashed or formatted will lead to no matches.
*/
@Json(name = "addresses")
val hashedAddresses: List<String>,
/**
* Required. The algorithm the client is using to encode the addresses. This should be one of the available options from /hash_details.
*/
@Json(name = "algorithm")
val algorithm: String,
/**
* Required. The pepper from /hash_details. This is required even when the algorithm does not make use of it.
*/
@Json(name = "pepper")
val pepper: String
)

View File

@ -0,0 +1,33 @@
/*
* 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.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Ref: https://github.com/matrix-org/matrix-doc/blob/hs/hash-identity/proposals/2134-identity-hash-lookup.md
*/
@JsonClass(generateAdapter = true)
internal data class IdentityLookUpResponse(
/**
* Required. Any applicable mappings of addresses to Matrix User IDs. Addresses which do not have associations will
* not be included, which can make this property be an empty object.
*/
@Json(name = "mappings")
val mappings: Map<String, String>
)

View File

@ -0,0 +1,29 @@
/*
* 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.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class IdentityRegisterResponse(
/**
* Required. An opaque string representing the token to authenticate future requests to the identity server with.
*/
@Json(name = "token")
val token: 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.internal.session.identity.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class IdentityRequestOwnershipParams(
/**
* Required. The client secret that was supplied to the requestToken call.
*/
@Json(name = "client_secret")
val clientSecret: String,
/**
* Required. The session ID, generated by the requestToken call.
*/
@Json(name = "sid")
val sid: String,
/**
* Required. The token generated by the requestToken call and sent to the user.
*/
@Json(name = "token")
val token: String
)

View File

@ -0,0 +1,82 @@
/*
* 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.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
// Just to consider common parameters
private interface IdentityRequestTokenBody {
/**
* Required. A unique string generated by the client, and used to identify the validation attempt.
* It must be a string consisting of the characters [0-9a-zA-Z.=_-].
* Its length must not exceed 255 characters and it must not be empty.
*/
val clientSecret: String
val sendAttempt: Int
}
@JsonClass(generateAdapter = true)
internal data class IdentityRequestTokenForEmailBody(
@Json(name = "client_secret")
override val clientSecret: String,
/**
* Required. The server will only send an email if the send_attempt is a number greater than the most
* recent one which it has seen, scoped to that email + client_secret pair. This is to avoid repeatedly
* sending the same email in the case of request retries between the POSTing user and the identity server.
* The client should increment this value if they desire a new email (e.g. a reminder) to be sent.
* If they do not, the server should respond with success but not resend the email.
*/
@Json(name = "send_attempt")
override val sendAttempt: Int,
/**
* Required. The email address to validate.
*/
@Json(name = "email")
val email: String
) : IdentityRequestTokenBody
@JsonClass(generateAdapter = true)
internal data class IdentityRequestTokenForMsisdnBody(
@Json(name = "client_secret")
override val clientSecret: String,
/**
* Required. The server will only send an SMS if the send_attempt is a number greater than the most recent one
* which it has seen, scoped to that country + phone_number + client_secret triple. This is to avoid repeatedly
* sending the same SMS in the case of request retries between the POSTing user and the identity server.
* The client should increment this value if they desire a new SMS (e.g. a reminder) to be sent.
*/
@Json(name = "send_attempt")
override val sendAttempt: Int,
/**
* Required. The phone number to validate.
*/
@Json(name = "phone_number")
val phoneNumber: String,
/**
* Required. The two-letter uppercase ISO-3166-1 alpha-2 country code that the number in phone_number
* should be parsed as if it were dialled from.
*/
@Json(name = "country")
val countryCode: String
) : IdentityRequestTokenBody

View File

@ -0,0 +1,31 @@
/*
* 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.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class IdentityRequestTokenResponse(
/**
* Required. The session ID. Session IDs are opaque strings generated by the identity server.
* They must consist entirely of the characters [0-9a-zA-Z.=_-].
* Their length must not exceed 255 characters and they must not be empty.
*/
@Json(name = "sid")
val sid: String
)

View File

@ -0,0 +1,66 @@
/*
* 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.todelete
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
import io.realm.Realm
import io.realm.RealmQuery
import javax.inject.Inject
// There will be a duplicated class when Integration manager will be merged, so delete this one
internal class AccountDataDataSource @Inject constructor(private val monarchy: Monarchy,
private val accountDataMapper: AccountDataMapper) {
fun getAccountDataEvent(type: String): UserAccountDataEvent? {
return getAccountDataEvents(setOf(type)).firstOrNull()
}
fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> {
return Transformations.map(getLiveAccountDataEvents(setOf(type))) {
it.firstOrNull()?.toOptional()
}
}
fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> {
return monarchy.fetchAllMappedSync(
{ accountDataEventsQuery(it, types) },
accountDataMapper::map
)
}
fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> {
return monarchy.findAllMappedWithChanges(
{ accountDataEventsQuery(it, types) },
accountDataMapper::map
)
}
private fun accountDataEventsQuery(realm: Realm, types: Set<String>): RealmQuery<UserAccountDataEntity> {
val query = realm.where(UserAccountDataEntity::class.java)
if (types.isNotEmpty()) {
query.`in`(UserAccountDataEntityFields.TYPE, types.toTypedArray())
}
return query
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.todelete
import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
import javax.inject.Inject
// There will be a duplicated class when Integration manager will be merged, so delete this one
internal class AccountDataMapper @Inject constructor(moshi: Moshi) {
private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE)
fun map(entity: UserAccountDataEntity): UserAccountDataEvent {
return UserAccountDataEvent(
type = entity.type ?: "",
content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap()
)
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.todelete
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
// There will be a duplicated class when Integration manager will be merged, so delete this one
inline fun <T> LiveData<T>.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) {
this.observe(owner, Observer { observer(it) })
}
inline fun <T> LiveData<T>.observeNotNull(owner: LifecycleOwner, crossinline observer: (T) -> Unit) {
this.observe(owner, Observer { it?.run(observer) })
}

View File

@ -91,7 +91,7 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
fun start() { fun start() {
lifecycleRegistry.currentState = Lifecycle.State.STARTED lifecycleRegistry.currentState = Lifecycle.State.STARTED
accountDataDataSource accountDataDataSource
.getLiveAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS) .getLiveAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
.observeNotNull(lifecycleOwner) { .observeNotNull(lifecycleOwner) {
val allowedWidgetsContent = it.getOrNull()?.content?.toModel<AllowedWidgetsContent>() val allowedWidgetsContent = it.getOrNull()?.content?.toModel<AllowedWidgetsContent>()
if (allowedWidgetsContent != null) { if (allowedWidgetsContent != null) {
@ -99,7 +99,7 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
} }
} }
accountDataDataSource accountDataDataSource
.getLiveAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_INTEGRATION_PROVISIONING) .getLiveAccountDataEvent(UserAccountData.TYPE_INTEGRATION_PROVISIONING)
.observeNotNull(lifecycleOwner) { .observeNotNull(lifecycleOwner) {
val integrationProvisioningContent = it.getOrNull()?.content?.toModel<IntegrationProvisioningContent>() val integrationProvisioningContent = it.getOrNull()?.content?.toModel<IntegrationProvisioningContent>()
if (integrationProvisioningContent != null) { if (integrationProvisioningContent != null) {
@ -142,7 +142,7 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
* Returns false if the user as disabled integration manager feature * Returns false if the user as disabled integration manager feature
*/ */
fun isIntegrationEnabled(): Boolean { fun isIntegrationEnabled(): Boolean {
val integrationProvisioningData = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_INTEGRATION_PROVISIONING) val integrationProvisioningData = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_INTEGRATION_PROVISIONING)
val integrationProvisioningContent = integrationProvisioningData?.content?.toModel<IntegrationProvisioningContent>() val integrationProvisioningContent = integrationProvisioningData?.content?.toModel<IntegrationProvisioningContent>()
return integrationProvisioningContent?.enabled ?: false return integrationProvisioningContent?.enabled ?: false
} }
@ -163,7 +163,7 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
} }
fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable { fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS) val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>() val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
val newContent = if (currentContent == null) { val newContent = if (currentContent == null) {
val allowedWidget = mapOf(stateEventId to allowed) val allowedWidget = mapOf(stateEventId to allowed)
@ -183,13 +183,13 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
} }
fun isWidgetAllowed(stateEventId: String): Boolean { fun isWidgetAllowed(stateEventId: String): Boolean {
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS) val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>() val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
return currentContent?.widgets?.get(stateEventId) ?: false return currentContent?.widgets?.get(stateEventId) ?: false
} }
fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable { fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS) val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>() val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
val newContent = if (currentContent == null) { val newContent = if (currentContent == null) {
val nativeAllowedWidgets = mapOf(widgetType to mapOf(domain to allowed)) val nativeAllowedWidgets = mapOf(widgetType to mapOf(domain to allowed))
@ -213,7 +213,7 @@ internal class IntegrationManager @Inject constructor(private val taskExecutor:
} }
fun isNativeWidgetAllowed(widgetType: String, domain: String?): Boolean { fun isNativeWidgetAllowed(widgetType: String, domain: String?): Boolean {
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.ACCOUNT_DATA_TYPE_ALLOWED_WIDGETS) val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>() val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
return currentContent?.native?.get(widgetType)?.get(domain) ?: false return currentContent?.native?.get(widgetType)?.get(domain) ?: false
} }

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.session.profile
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Class representing the ThreePids response
*/
@JsonClass(generateAdapter = true)
internal data class AccountThreePidsResponse(
@Json(name = "threepids")
val threePids: List<ThirdPartyIdentifier>? = null
)

Some files were not shown because too many files have changed in this diff Show More