Skip to content

Commit

Permalink
Merge pull request #1354 from vector-im/feature/identity
Browse files Browse the repository at this point in the history
Identity server
  • Loading branch information
bmarty authored May 18, 2020
2 parents e0c3f36 + 92985fc commit f47bef7
Show file tree
Hide file tree
Showing 225 changed files with 7,184 additions and 402 deletions.
3 changes: 3 additions & 0 deletions .idea/dictionaries/bmarty.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Changes in RiotX 0.21.0 (2020-XX-XX)
===================================================

Features ✨:
- Identity server support (#607)
- Switch language support (#41)

Improvements 🙌:
Expand Down
92 changes: 92 additions & 0 deletions docs/identity_server.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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.group.GroupSummaryQueryParams
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.room.RoomSummaryQueryParams
import im.vector.matrix.android.api.session.room.model.RoomSummary
Expand Down Expand Up @@ -94,6 +95,11 @@ class RxSession(private val session: Session) {
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 {
session.createRoom(roomParams, it)
}
Expand Down
3 changes: 3 additions & 0 deletions matrix-sdk-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ dependencies {
// Bus
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'
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ class CommonTestHelper(context: Context) {
fun syncSession(session: Session) {
val lock = CountDownLatch(1)

session.open()
GlobalScope.launch(Dispatchers.Main) { session.open() }

session.startSync(true)

val syncLiveData = runBlocking(Dispatchers.Main) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
assertNotNull(eventWireContent.get("session_id"))
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)
assertEquals(roomId, event.roomId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

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
val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ class KeyShareTests : InstrumentedTest {

// Mark the device as trusted
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
aliceSession2.sessionParams.credentials.deviceId ?: "")
aliceSession2.sessionParams.deviceId ?: "")

// Re request
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
Expand Down Expand Up @@ -253,12 +253,12 @@ class KeyShareTests : InstrumentedTest {
})

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)

mTestHelper.waitWithLatch { 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
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,7 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(signature.valid)
assertNotNull(signature.device)
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)
cryptoTestData.cleanUp(mTestHelper)
Expand Down Expand Up @@ -997,7 +997,7 @@ class KeysBackupTest : InstrumentedTest {
keysBackup.backupAllGroupSessions(null, it)
}

val oldDeviceId = cryptoTestData.firstSession.sessionParams.credentials.deviceId!!
val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId!!
val oldKeyBackupVersion = keysBackup.currentBackupVersion
val aliceUserId = cryptoTestData.firstSession.myUserId

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,15 +579,15 @@ class SASTest : InstrumentedTest {
requestID!!,
cryptoTestData.roomId,
bobSession.myUserId,
bobSession.sessionParams.credentials.deviceId!!,
bobSession.sessionParams.deviceId!!,
null)

bobVerificationService.beginKeyVerificationInDMs(
VerificationMethod.SAS,
requestID!!,
cryptoTestData.roomId,
aliceSession.myUserId,
aliceSession.sessionParams.credentials.deviceId!!,
aliceSession.sessionParams.deviceId!!,
null)

// we should reach SHOW SAS on both
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.HomeServerConnectionConfig
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.registration.RegistrationWizard
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
Expand All @@ -37,6 +36,11 @@ interface AuthenticationService {
*/
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.
*/
Expand Down Expand Up @@ -74,15 +78,6 @@ interface AuthenticationService {
*/
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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,48 @@ package im.vector.matrix.android.api.auth.data
* You don't have to manually instantiate it.
*/
data class SessionParams(
/**
* Please consider using shortcuts instead
*/
val credentials: Credentials,

/**
* Please consider using shortcuts instead
*/
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
)
) {
/*
* 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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ data class MatrixError(
// For M_LIMIT_EXCEEDED
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
// 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 {
Expand Down Expand Up @@ -129,6 +132,11 @@ data class MatrixError(
/** (Not documented yet) */
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"
const val LIMIT_TYPE_MAU = "monthly_active_user"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.group.GroupService
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.pushers.PushersService
import im.vector.matrix.android.api.session.room.RoomDirectoryService
Expand All @@ -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.sync.FilterService
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

/**
Expand All @@ -54,6 +56,7 @@ interface Session :
SignOutService,
FilterService,
FileService,
TermsService,
ProfileService,
PushRuleService,
PushersService,
Expand All @@ -77,7 +80,7 @@ interface Session :
* Useful shortcut to get access to the userId
*/
val myUserId: String
get() = sessionParams.credentials.userId
get() = sessionParams.userId

/**
* The sessionId
Expand Down Expand Up @@ -145,6 +148,11 @@ interface Session :
*/
fun cryptoService(): CryptoService

/**
* Returns the identity service associated with the session
*/
fun identityService(): IdentityService

/**
* Add a listener to the session.
* @param listener the listener to add.
Expand Down
Loading

0 comments on commit f47bef7

Please sign in to comment.