Merge branch 'develop' into feature/power_level

This commit is contained in:
ganfra 2020-06-10 17:21:04 +02:00
commit 579d4f7a5b
47 changed files with 575 additions and 116 deletions

View file

@ -10,6 +10,9 @@ Improvements 🙌:
- Hide "X made no changes" event by default in timeline (#1430) - Hide "X made no changes" event by default in timeline (#1430)
- Hide left rooms in breadcrumbs (#766) - Hide left rooms in breadcrumbs (#766)
- Handle PowerLevel properly (#627) - Handle PowerLevel properly (#627)
- Correctly handle SSO login redirection
- SSO login is now performed in the default browser, or in Chrome Custom tab if available (#1400)
- Improve checking of homeserver version support (#1442)
Bugfix 🐛: Bugfix 🐛:
- Switch theme is not fully taken into account without restarting the app - Switch theme is not fully taken into account without restarting the app
@ -17,6 +20,8 @@ Bugfix 🐛:
- Reply composer overlay stays on screen too long after send (#1169) - Reply composer overlay stays on screen too long after send (#1169)
- Fix navigation bar icon contrast on API in [21,27[ (#1342) - Fix navigation bar icon contrast on API in [21,27[ (#1342)
- Fix status bar icon contrast on API in [21,23[ - Fix status bar icon contrast on API in [21,23[
- Wrong /query request (#1444)
- Make Credentials.homeServer optional because it is deprecated (#1443)
Translations 🗣: Translations 🗣:
- -
@ -29,6 +34,7 @@ Build 🧱:
Other changes: Other changes:
- Send plain text in the body of events containing formatted body, as per https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes - Send plain text in the body of events containing formatted body, as per https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
- Update link to Modular url from "https://modular.im/" to "https://modular.im/services/matrix-hosting-riot" and open it using ChromeCustomTab
Changes in RiotX 0.21.0 (2020-05-28) Changes in RiotX 0.21.0 (2020-05-28)
=================================================== ===================================================

View file

@ -2,7 +2,7 @@
This document describes the flow of signin to a homeserver, and also the flow when user want to reset his password. Examples come from the `matrix.org` homeserver. This document describes the flow of signin to a homeserver, and also the flow when user want to reset his password. Examples come from the `matrix.org` homeserver.
## Sign up flows ## Sign in flows
### Get the flow ### Get the flow
@ -58,7 +58,7 @@ We get credential (200)
```json ```json
{ {
"user_id": "@alice:matrix.org", "user_id": "@alice:matrix.org",
"access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gfnYrSypfdTtkNXIuNWx1KgowMDJmc2lnbmF0dXJlIOsh1XqeAkXexh4qcofl_aR4kHJoSOWYGOhE7-ubX-DZCg", "access_token": "MDAxOGxvY2F0aW9uIG1hdHREDACTEDb2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lr",
"home_server": "matrix.org", "home_server": "matrix.org",
"device_id": "GTVREDALBF", "device_id": "GTVREDALBF",
"well_known": { "well_known": {
@ -117,7 +117,7 @@ We get the credentials (200)
```json ```json
{ {
"user_id": "@alice:matrix.org", "user_id": "@alice:matrix.org",
"access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3JnCjAwMTZjaWQgdHlwZSA9IGFjY2VzcwowMDIxY2lkIG5vbmNlID0gNjtDY0MwRlNPSFFoOC5wOgowMDJmc2lnbmF0dXJlIGiTRm1mYLLxQywxOh3qzQVT8HoEorSokEP2u-bAwtnYCg", "access_token": "MDAxOGxvY2F0aW9uIG1hdHJpeC5vcmREDACTEDZXJfaWQgPSBAYmVub2l0MDgxNjptYXRyaXgub3Jnfrfdegfszsefddvf",
"home_server": "matrix.org", "home_server": "matrix.org",
"device_id": "WBSREDASND", "device_id": "WBSREDASND",
"well_known": { "well_known": {
@ -145,12 +145,59 @@ Not supported yet in RiotX
"flows": [ "flows": [
{ {
"type": "m.login.sso" "type": "m.login.sso"
},
{
"type": "m.login.token"
} }
] ]
} }
``` ```
In this case, the user can click on "Sign in with SSO" and the web screen will be displayed on the page `https://homeserver.with.sso/_matrix/static/client/login/` and the credentials will be passed back to the native code through the JS bridge In this case, the user can click on "Sign in with SSO" and the native web browser, or a ChromeCustomTab if the device supports it, will be launched on the page
> https://homeserver.with.sso/_matrix/client/r0/login/sso/redirect?redirectUrl=riotx%3A%2F%2Friotx
The parameter `redirectUrl` is set to `riotx://riotx`.
ChromeCustomTabs are an intermediate way to display a WebPage, between a WebView and using the external browser. More info can be found [here](https://developer.chrome.com/multidevice/android/customtabs)
The browser will then take care of the SSO login, which may include creating a third party account, entering an email, settings a display name, or any other possibilities.
During the process, user may be asked to validate an email by clicking on a link it contains. The link has to be opened in the browser which initiates the authentication. This is why we cannot use WebView anymore.
Once the process is finished, the web page will call the `redirectUrl` with an extra parameter `loginToken`
> riotx://riotx?loginToken=MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy
This navigation is intercepted by RiotX by the `LoginActivity`, which will then ask the homeserver to convert this `loginToken` to an access token
> curl -X POST --data $'{"type":"m.login.token","token":"MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy"}' 'https://homeserver.with.sso/_matrix/client/r0/login'
```json
{
"type": "m.login.token",
"token": "MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy"
}
```
We get the credentials (200)
```json
{
"user_id": "@alice:homeserver.with.sso",
"access_token": "MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVyIGtleQowMDEwY2lkIGdlbiA9IDEKMDAyY2NpZCB1c2",
"home_server": "homeserver.with.sso",
"device_id": "DETBTVAHCH",
"well_known": {
"m.homeserver": {
"base_url": "https:\/\/homeserver.with.sso\/"
},
"m.identity_server": {
"base_url": "https:\/\/vector.im"
}
}
}
```
## Reset password ## Reset password

View file

@ -32,6 +32,6 @@ const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
* Path to use when the client want to connect using SSO * Path to use when the client want to connect using SSO
* Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login * Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
*/ */
const val SSO_FALLBACK_PATH = "/_matrix/client/r0/login/sso/redirect" const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
const val SSO_REDIRECT_URL_PARAM = "redirectUrl" const val SSO_REDIRECT_URL_PARAM = "redirectUrl"

View file

@ -45,7 +45,7 @@ data class Credentials(
* @Deprecated. Clients should extract the server_name from user_id (by splitting at the first colon) * @Deprecated. Clients should extract the server_name from user_id (by splitting at the first colon)
* if they require it. Note also that homeserver is not spelt this way. * if they require it. Note also that homeserver is not spelt this way.
*/ */
@Json(name = "home_server") val homeServer: String, @Json(name = "home_server") val homeServer: String?,
/** /**
* ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified. * ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified.
*/ */

View file

@ -16,12 +16,10 @@
package im.vector.matrix.android.api.auth.data package im.vector.matrix.android.api.auth.data
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse // Either a list of supported login types, or an error if the homeserver is outdated
// Either a LoginFlowResponse, or an error if the homeserver is outdated
sealed class LoginFlowResult { sealed class LoginFlowResult {
data class Success( data class Success(
val loginFlowResponse: LoginFlowResponse, val supportedLoginTypes: List<String>,
val isLoginAndRegistrationSupported: Boolean, val isLoginAndRegistrationSupported: Boolean,
val homeServerUrl: String val homeServerUrl: String
) : LoginFlowResult() ) : LoginFlowResult()

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.auth.data package im.vector.matrix.android.api.auth.data
object LoginFlowTypes { object LoginFlowTypes {
const val PASSWORD = "m.login.password" const val PASSWORD = "m.login.password"

View file

@ -34,6 +34,12 @@ interface LoginWizard {
deviceName: String, deviceName: String,
callback: MatrixCallback<Session>): Cancelable callback: MatrixCallback<Session>): Cancelable
/**
* Exchange a login token to an access token
*/
fun loginWithToken(loginToken: String,
callback: MatrixCallback<Session>): Cancelable
/** /**
* Reset user password * Reset user password
*/ */

View file

@ -21,15 +21,12 @@ sealed class Stage(open val mandatory: Boolean) {
// m.login.recaptcha // m.login.recaptcha
data class ReCaptcha(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory) data class ReCaptcha(override val mandatory: Boolean, val publicKey: String) : Stage(mandatory)
// m.login.oauth2
// m.login.email.identity // m.login.email.identity
data class Email(override val mandatory: Boolean) : Stage(mandatory) data class Email(override val mandatory: Boolean) : Stage(mandatory)
// m.login.msisdn // m.login.msisdn
data class Msisdn(override val mandatory: Boolean) : Stage(mandatory) data class Msisdn(override val mandatory: Boolean) : Stage(mandatory)
// m.login.token
// m.login.dummy, can be mandatory if there is no other stages. In this case the account cannot be created by just sending a username // m.login.dummy, can be mandatory if there is no other stages. In this case the account cannot be created by just sending a username
// and a password, the dummy stage has to be done // and a password, the dummy stage has to be done
data class Dummy(override val mandatory: Boolean) : Stage(mandatory) data class Dummy(override val mandatory: Boolean) : Stage(mandatory)

View file

@ -17,16 +17,17 @@
package im.vector.matrix.android.internal.auth package im.vector.matrix.android.internal.auth
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.Versions
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.PasswordLoginParams import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
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.data.TokenLoginParams
import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
import im.vector.matrix.android.internal.auth.registration.RegistrationParams import im.vector.matrix.android.internal.auth.registration.RegistrationParams
import im.vector.matrix.android.internal.auth.registration.SuccessResult import im.vector.matrix.android.internal.auth.registration.SuccessResult
import im.vector.matrix.android.internal.auth.registration.ValidationCodeBody import im.vector.matrix.android.internal.auth.registration.ValidationCodeBody
import im.vector.matrix.android.internal.auth.version.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.Body import retrofit2.http.Body
@ -54,7 +55,7 @@ internal interface AuthAPI {
fun versions(): Call<Versions> fun versions(): Call<Versions>
/** /**
* Register to the homeserver * Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete
* Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management * Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management
*/ */
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
@ -91,6 +92,11 @@ internal interface AuthAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
fun login(@Body loginParams: PasswordLoginParams): Call<Credentials> fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>
// Unfortunately we cannot use interface for @Body parameter, so I duplicate the method for the type TokenLoginParams
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
fun login(@Body loginParams: TokenLoginParams): Call<Credentials>
/** /**
* Ask the homeserver to reset the password associated with the provided email. * Ask the homeserver to reset the password associated with the provided email.
*/ */

View file

@ -23,9 +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.Versions
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.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
@ -40,6 +37,9 @@ 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.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.version.Versions
import im.vector.matrix.android.internal.auth.version.isLoginAndRegistrationSupportedBySdk
import im.vector.matrix.android.internal.auth.version.isSupportedBySdk
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
@ -236,7 +236,7 @@ internal class DefaultAuthenticationService @Inject constructor(
val loginFlowResponse = executeRequest<LoginFlowResponse>(null) { val loginFlowResponse = executeRequest<LoginFlowResponse>(null) {
apiCall = authAPI.getLoginFlows() apiCall = authAPI.getLoginFlows()
} }
LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl) LoginFlowResult.Success(loginFlowResponse.flows.orEmpty().mapNotNull { it.type }, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
} else { } else {
// Not supported // Not supported
LoginFlowResult.OutdatedHomeserver LoginFlowResult.OutdatedHomeserver

View file

@ -20,7 +20,19 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class LoginFlowResponse( internal data class LoginFlowResponse(
/**
* The homeserver's supported login types
*/
@Json(name = "flows") @Json(name = "flows")
val flows: List<InteractiveAuthenticationFlow> val flows: List<LoginFlow>?
)
@JsonClass(generateAdapter = true)
internal data class LoginFlow(
/**
* The login type. This is supplied as the type when logging in.
*/
@Json(name = "type")
val type: String?
) )

View file

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.auth.data
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
/** /**
* Ref: * Ref:

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.auth.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
@JsonClass(generateAdapter = true)
internal data class TokenLoginParams(
@Json(name = "type") override val type: String = LoginFlowTypes.TOKEN,
@Json(name = "token") val token: String
) : LoginParams

View file

@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.auth.SessionCreator import im.vector.matrix.android.internal.auth.SessionCreator
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.auth.data.ThreePidMedium import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import im.vector.matrix.android.internal.auth.data.TokenLoginParams
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.registration.AddThreePidRegistrationParams import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
@ -65,6 +66,22 @@ internal class DefaultLoginWizard(
} }
} }
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint
*/
override fun loginWithToken(loginToken: String, callback: MatrixCallback<Session>): Cancelable {
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
val loginParams = TokenLoginParams(
token = loginToken
)
val credentials = executeRequest<Credentials>(null) {
apiCall = authAPI.login(loginParams)
}
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
}
}
private suspend fun loginInternal(login: String, private suspend fun loginInternal(login: String,
password: String, password: String,
deviceName: String) = withContext(coroutineDispatchers.computation) { deviceName: String) = withContext(coroutineDispatchers.computation) {

View file

@ -18,7 +18,7 @@ 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.internal.auth.data.LoginFlowTypes import im.vector.matrix.android.api.auth.data.LoginFlowTypes
/** /**
* Open class, parent to all possible authentication parameters * Open class, parent to all possible authentication parameters

View file

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.auth.registration
import dagger.Lazy import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
import im.vector.matrix.android.api.auth.registration.RegisterThreePid import im.vector.matrix.android.api.auth.registration.RegisterThreePid
import im.vector.matrix.android.api.auth.registration.RegistrationResult import im.vector.matrix.android.api.auth.registration.RegistrationResult
import im.vector.matrix.android.api.auth.registration.RegistrationWizard import im.vector.matrix.android.api.auth.registration.RegistrationWizard
@ -28,7 +29,6 @@ import im.vector.matrix.android.api.util.NoOpCancellable
import im.vector.matrix.android.internal.auth.AuthAPI import im.vector.matrix.android.internal.auth.AuthAPI
import im.vector.matrix.android.internal.auth.PendingSessionStore import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.auth.SessionCreator import im.vector.matrix.android.internal.auth.SessionCreator
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.auth.db.PendingSessionData 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

View file

@ -18,12 +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.auth.data.LoginFlowTypes
import im.vector.matrix.android.api.auth.registration.FlowResult import im.vector.matrix.android.api.auth.registration.FlowResult
import im.vector.matrix.android.api.auth.registration.Stage import im.vector.matrix.android.api.auth.registration.Stage
import im.vector.matrix.android.api.auth.registration.TermPolicies import im.vector.matrix.android.api.auth.registration.TermPolicies
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class RegistrationFlowResponse( data class RegistrationFlowResponse(

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.version
/**
* Values will take the form "rX.Y.Z".
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions
*/
internal data class HomeServerVersion(
val major: Int,
val minor: Int,
val patch: Int
) : Comparable<HomeServerVersion> {
override fun compareTo(other: HomeServerVersion): Int {
return when {
major > other.major -> 1
major < other.major -> -1
minor > other.minor -> 1
minor < other.minor -> -1
patch > other.patch -> 1
patch < other.patch -> -1
else -> 0
}
}
companion object {
internal val pattern = Regex("""r(\d+)\.(\d+)\.(\d+)""")
internal fun parse(value: String): HomeServerVersion? {
val result = pattern.matchEntire(value) ?: return null
return HomeServerVersion(
major = result.groupValues[1].toInt(),
minor = result.groupValues[2].toInt(),
patch = result.groupValues[3].toInt()
)
}
val r0_0_0 = HomeServerVersion(major = 0, minor = 0, patch = 0)
val r0_1_0 = HomeServerVersion(major = 0, minor = 1, patch = 0)
val r0_2_0 = HomeServerVersion(major = 0, minor = 2, patch = 0)
val r0_3_0 = HomeServerVersion(major = 0, minor = 3, patch = 0)
val r0_4_0 = HomeServerVersion(major = 0, minor = 4, patch = 0)
val r0_5_0 = HomeServerVersion(major = 0, minor = 5, patch = 0)
val r0_6_0 = HomeServerVersion(major = 0, minor = 6, patch = 0)
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright 2018 New Vector Ltd * Copyright (c) 2020 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.auth.data package im.vector.matrix.android.internal.auth.version
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@ -38,7 +38,7 @@ import com.squareup.moshi.JsonClass
* </pre> * </pre>
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class Versions( internal data class Versions(
@Json(name = "versions") @Json(name = "versions")
val supportedVersions: List<String>? = null, val supportedVersions: List<String>? = null,
@ -46,15 +46,6 @@ data class Versions(
val unstableFeatures: Map<String, Boolean>? = null val unstableFeatures: Map<String, Boolean>? = null
) )
// MatrixClientServerAPIVersion
private const val r0_0_1 = "r0.0.1"
private const val r0_1_0 = "r0.1.0"
private const val r0_2_0 = "r0.2.0"
private const val r0_3_0 = "r0.3.0"
private const val r0_4_0 = "r0.4.0"
private const val r0_5_0 = "r0.5.0"
private const val r0_6_0 = "r0.6.0"
// MatrixVersionsFeature // MatrixVersionsFeature
private const val FEATURE_LAZY_LOAD_MEMBERS = "m.lazy_load_members" private const val FEATURE_LAZY_LOAD_MEMBERS = "m.lazy_load_members"
private const val FEATURE_REQUIRE_IDENTITY_SERVER = "m.require_identity_server" private const val FEATURE_REQUIRE_IDENTITY_SERVER = "m.require_identity_server"
@ -64,14 +55,14 @@ private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind"
/** /**
* Return true if the SDK supports this homeserver version * Return true if the SDK supports this homeserver version
*/ */
fun Versions.isSupportedBySdk(): Boolean { internal fun Versions.isSupportedBySdk(): Boolean {
return supportLazyLoadMembers() return supportLazyLoadMembers()
} }
/** /**
* Return true if the SDK supports this homeserver version for login and registration * Return true if the SDK supports this homeserver version for login and registration
*/ */
fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean { internal fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean {
return !doesServerRequireIdentityServerParam() return !doesServerRequireIdentityServerParam()
&& doesServerAcceptIdentityAccessToken() && doesServerAcceptIdentityAccessToken()
&& doesServerSeparatesAddAndBind() && doesServerSeparatesAddAndBind()
@ -83,7 +74,7 @@ fun Versions.isLoginAndRegistrationSupportedBySdk(): Boolean {
* @return true if the server support the lazy loading of room members * @return true if the server support the lazy loading of room members
*/ */
private fun Versions.supportLazyLoadMembers(): Boolean { private fun Versions.supportLazyLoadMembers(): Boolean {
return supportedVersions?.contains(r0_5_0) == true return getMaxVersion() >= HomeServerVersion.r0_5_0
|| unstableFeatures?.get(FEATURE_LAZY_LOAD_MEMBERS) == true || unstableFeatures?.get(FEATURE_LAZY_LOAD_MEMBERS) == true
} }
@ -92,7 +83,7 @@ private fun Versions.supportLazyLoadMembers(): Boolean {
* adding a 3pid or resetting password. * adding a 3pid or resetting password.
*/ */
private fun Versions.doesServerRequireIdentityServerParam(): Boolean { private fun Versions.doesServerRequireIdentityServerParam(): Boolean {
if (supportedVersions?.contains(r0_6_0) == true) return false if (getMaxVersion() >= HomeServerVersion.r0_6_0) return false
return unstableFeatures?.get(FEATURE_REQUIRE_IDENTITY_SERVER) ?: true return unstableFeatures?.get(FEATURE_REQUIRE_IDENTITY_SERVER) ?: true
} }
@ -101,11 +92,18 @@ private fun Versions.doesServerRequireIdentityServerParam(): Boolean {
* Some homeservers may trigger errors if they are not prepared for the new parameter. * Some homeservers may trigger errors if they are not prepared for the new parameter.
*/ */
private fun Versions.doesServerAcceptIdentityAccessToken(): Boolean { private fun Versions.doesServerAcceptIdentityAccessToken(): Boolean {
return supportedVersions?.contains(r0_6_0) == true return getMaxVersion() >= HomeServerVersion.r0_6_0
|| unstableFeatures?.get(FEATURE_ID_ACCESS_TOKEN) ?: false || unstableFeatures?.get(FEATURE_ID_ACCESS_TOKEN) ?: false
} }
private fun Versions.doesServerSeparatesAddAndBind(): Boolean { private fun Versions.doesServerSeparatesAddAndBind(): Boolean {
return supportedVersions?.contains(r0_6_0) == true return getMaxVersion() >= HomeServerVersion.r0_6_0
|| unstableFeatures?.get(FEATURE_SEPARATE_ADD_AND_BIND) ?: false || unstableFeatures?.get(FEATURE_SEPARATE_ADD_AND_BIND) ?: false
} }
private fun Versions.getMaxVersion(): HomeServerVersion {
return supportedVersions
?.mapNotNull { HomeServerVersion.parse(it) }
?.max()
?: HomeServerVersion.r0_0_0
}

View file

@ -36,7 +36,7 @@ internal data class KeysQueryBody(
* A map from user ID, to a list of device IDs, or to an empty list to indicate all devices for the corresponding user. * A map from user ID, to a list of device IDs, or to an empty list to indicate all devices for the corresponding user.
*/ */
@Json(name = "device_keys") @Json(name = "device_keys")
val deviceKeys: Map<String, Any>, val deviceKeys: Map<String, List<String>>,
/** /**
* If the client is fetching keys as a result of a device update received in a sync request, this should be the 'since' token * If the client is fetching keys as a result of a device update received in a sync request, this should be the 'since' token

View file

@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.crypto.model.rest
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.internal.auth.data.LoginFlowTypes import im.vector.matrix.android.api.auth.data.LoginFlowTypes
/** /**
* This class provides the authentication data by using user and password * This class provides the authentication data by using user and password

View file

@ -16,7 +16,7 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes import im.vector.matrix.android.api.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth

View file

@ -27,7 +27,7 @@ import javax.inject.Inject
internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Params, KeysQueryResponse> { internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Params, KeysQueryResponse> {
data class Params( data class Params(
// the list of users to get keys for. // the list of users to get keys for.
val userIds: List<String>?, val userIds: List<String>,
// the up-to token // the up-to token
val token: String? val token: String?
) )
@ -39,7 +39,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(
) : DownloadKeysForUsersTask { ) : DownloadKeysForUsersTask {
override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse { override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse {
val downloadQuery = params.userIds?.associateWith { emptyMap<String, Any>() }.orEmpty() val downloadQuery = params.userIds.associateWith { emptyList<String>() }
val body = KeysQueryBody( val body = KeysQueryBody(
deviceKeys = downloadQuery, deviceKeys = downloadQuery,

View file

@ -16,7 +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.auth.version.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

View file

@ -17,10 +17,10 @@
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.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.auth.version.Versions
import im.vector.matrix.android.internal.auth.version.isLoginAndRegistrationSupportedBySdk
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.di.UserId

View file

@ -27,7 +27,7 @@ internal interface WidgetsAPI {
/** /**
* register to the server * register to the server
* *
* @param requestOpenIdTokenResponse the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961) * @param body the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961)
*/ */
@POST("register") @POST("register")
fun register(@Body body: RequestOpenIdTokenResponse, @Query("v") version: String?): Call<RegisterWidgetResponse> fun register(@Body body: RequestOpenIdTokenResponse, @Query("v") version: String?): Call<RegisterWidgetResponse>

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.api.auth.data
import im.vector.matrix.android.internal.auth.version.Versions
import im.vector.matrix.android.internal.auth.version.isSupportedBySdk
import org.amshove.kluent.shouldBe
import org.junit.Test
class VersionsKtTest {
@Test
fun isSupportedBySdkTooLow() {
Versions(supportedVersions = listOf("r0.4.0")).isSupportedBySdk() shouldBe false
Versions(supportedVersions = listOf("r0.4.1")).isSupportedBySdk() shouldBe false
}
@Test
fun isSupportedBySdkUnstable() {
Versions(supportedVersions = listOf("r0.4.0"), unstableFeatures = mapOf("m.lazy_load_members" to true)).isSupportedBySdk() shouldBe true
}
@Test
fun isSupportedBySdkOk() {
Versions(supportedVersions = listOf("r0.5.0")).isSupportedBySdk() shouldBe true
Versions(supportedVersions = listOf("r0.5.1")).isSupportedBySdk() shouldBe true
}
// Was not working
@Test
fun isSupportedBySdkLater() {
Versions(supportedVersions = listOf("r0.6.0")).isSupportedBySdk() shouldBe true
Versions(supportedVersions = listOf("r0.6.1")).isSupportedBySdk() shouldBe true
}
// Cover cases of issue #1442
@Test
fun isSupportedBySdk1442() {
Versions(supportedVersions = listOf("r0.5.0", "r0.6.0")).isSupportedBySdk() shouldBe true
Versions(supportedVersions = listOf("r0.5.0", "r0.6.1")).isSupportedBySdk() shouldBe true
Versions(supportedVersions = listOf("r0.6.0")).isSupportedBySdk() shouldBe true
}
}

View file

@ -332,6 +332,9 @@ dependencies {
implementation 'com.google.android:flexbox:1.1.1' implementation 'com.google.android:flexbox:1.1.1'
implementation "androidx.autofill:autofill:$autofill_version" implementation "androidx.autofill:autofill:$autofill_version"
// Custom Tab
implementation 'androidx.browser:browser:1.2.0'
// Passphrase strength helper // Passphrase strength helper
implementation 'com.nulab-inc:zxcvbn:1.2.7' implementation 'com.nulab-inc:zxcvbn:1.2.7'

View file

@ -48,7 +48,19 @@
<activity android:name=".features.home.HomeActivity" /> <activity android:name=".features.home.HomeActivity" />
<activity <activity
android:name=".features.login.LoginActivity" android:name=".features.login.LoginActivity"
android:windowSoftInputMode="adjustResize" /> android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<!-- Add intent filter to handle redirection URL after SSO login in external browser -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="riotx" />
<data android:host="riotx" />
</intent-filter>
</activity>
<activity android:name=".features.media.ImageMediaViewerActivity" /> <activity android:name=".features.media.ImageMediaViewerActivity" />
<activity android:name=".features.media.BigImageViewerActivity" /> <activity android:name=".features.media.BigImageViewerActivity" />
<activity <activity

View file

@ -59,6 +59,7 @@ import im.vector.riotx.features.login.LoginResetPasswordSuccessFragment
import im.vector.riotx.features.login.LoginServerSelectionFragment import im.vector.riotx.features.login.LoginServerSelectionFragment
import im.vector.riotx.features.login.LoginServerUrlFormFragment import im.vector.riotx.features.login.LoginServerUrlFormFragment
import im.vector.riotx.features.login.LoginSignUpSignInSelectionFragment import im.vector.riotx.features.login.LoginSignUpSignInSelectionFragment
import im.vector.riotx.features.login.LoginSignUpSignInSsoFragment
import im.vector.riotx.features.login.LoginSplashFragment import im.vector.riotx.features.login.LoginSplashFragment
import im.vector.riotx.features.login.LoginWaitForEmailFragment import im.vector.riotx.features.login.LoginWaitForEmailFragment
import im.vector.riotx.features.login.LoginWebFragment import im.vector.riotx.features.login.LoginWebFragment
@ -217,6 +218,11 @@ interface FragmentModule {
@FragmentKey(LoginSignUpSignInSelectionFragment::class) @FragmentKey(LoginSignUpSignInSelectionFragment::class)
fun bindLoginSignUpSignInSelectionFragment(fragment: LoginSignUpSignInSelectionFragment): Fragment fun bindLoginSignUpSignInSelectionFragment(fragment: LoginSignUpSignInSelectionFragment): Fragment
@Binds
@IntoMap
@FragmentKey(LoginSignUpSignInSsoFragment::class)
fun bindLoginSignUpSignInSsoFragment(fragment: LoginSignUpSignInSsoFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(LoginSplashFragment::class) @FragmentKey(LoginSplashFragment::class)

View file

@ -21,10 +21,14 @@ import android.content.ActivityNotFoundException
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.Browser import android.provider.Browser
import android.provider.MediaStore import android.provider.MediaStore
import androidx.browser.customtabs.CustomTabsIntent
import androidx.browser.customtabs.CustomTabsSession
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.vector.riotx.BuildConfig import im.vector.riotx.BuildConfig
@ -64,6 +68,34 @@ fun openUrlInExternalBrowser(context: Context, uri: Uri?) {
} }
} }
/**
* Open url in custom tab or, if not available, in the default browser
* If several compatible browsers are installed, the user will be proposed to choose one.
* Ref: https://developer.chrome.com/multidevice/android/customtabs
*/
fun openUrlInChromeCustomTab(context: Context, session: CustomTabsSession?, url: String) {
try {
CustomTabsIntent.Builder()
.setToolbarColor(ContextCompat.getColor(context, R.color.riotx_background_light))
.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setNavigationBarColor(ContextCompat.getColor(context, R.color.riotx_header_panel_background_light))
}
}
.setNavigationBarColor(ContextCompat.getColor(context, R.color.riotx_background_light))
.setColorScheme(CustomTabsIntent.COLOR_SCHEME_LIGHT)
// Note: setting close button icon does not work
.setCloseButtonIcon(BitmapFactory.decodeResource(context.resources, R.drawable.ic_back_24dp))
.setStartAnimations(context, R.anim.enter_fade_in, R.anim.exit_fade_out)
.setExitAnimations(context, R.anim.enter_fade_in, R.anim.exit_fade_out)
.apply { session?.let { setSession(it) } }
.build()
.launchUrl(context, Uri.parse(url))
} catch (activityNotFoundException: ActivityNotFoundException) {
context.toast(R.string.error_no_external_application_found)
}
}
/** /**
* Open sound recorder external application * Open sound recorder external application
*/ */

View file

@ -16,6 +16,7 @@
package im.vector.riotx.features.crypto.recover package im.vector.riotx.features.crypto.recover
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.api.failure.toRegistrationFlowResponse import im.vector.matrix.android.api.failure.toRegistrationFlowResponse
@ -28,7 +29,6 @@ import im.vector.matrix.android.api.session.securestorage.EmptyKeySigner
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
import im.vector.matrix.android.api.session.securestorage.SsssKeySpec import im.vector.matrix.android.api.session.securestorage.SsssKeySpec
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion

View file

@ -16,4 +16,7 @@
package im.vector.riotx.features.login package im.vector.riotx.features.login
const val MODULAR_LINK = "https://modular.im/?utm_source=riot-x-android&utm_medium=native&utm_campaign=riot-x-android-authentication" const val MODULAR_LINK = "https://modular.im/services/matrix-hosting-riot" +
"?utm_source=riot-x-android" +
"&utm_medium=native" +
"&utm_campaign=riot-x-android-authentication"

View file

@ -24,6 +24,7 @@ sealed class LoginAction : VectorViewModelAction {
data class UpdateServerType(val serverType: ServerType) : LoginAction() data class UpdateServerType(val serverType: ServerType) : LoginAction()
data class UpdateHomeServer(val homeServerUrl: String) : LoginAction() data class UpdateHomeServer(val homeServerUrl: String) : LoginAction()
data class UpdateSignMode(val signMode: SignMode) : LoginAction() data class UpdateSignMode(val signMode: SignMode) : LoginAction()
data class LoginWithToken(val loginToken: String) : LoginAction()
data class WebLoginSuccess(val credentials: Credentials) : LoginAction() data class WebLoginSuccess(val credentials: Credentials) : LoginAction()
data class InitWith(val loginConfig: LoginConfig) : LoginAction() data class InitWith(val loginConfig: LoginConfig) : LoginAction()
data class ResetPassword(val email: String, val newPassword: String) : LoginAction() data class ResetPassword(val email: String, val newPassword: String) : LoginAction()

View file

@ -33,6 +33,7 @@ import com.airbnb.mvrx.viewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.auth.registration.FlowResult import im.vector.matrix.android.api.auth.registration.FlowResult
import im.vector.matrix.android.api.auth.registration.Stage import im.vector.matrix.android.api.auth.registration.Stage
import im.vector.matrix.android.api.extensions.tryThis
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.POP_BACK_STACK_EXCLUSIVE import im.vector.riotx.core.extensions.POP_BACK_STACK_EXCLUSIVE
@ -155,7 +156,11 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
is LoginViewEvents.OnSignModeSelected -> onSignModeSelected() is LoginViewEvents.OnSignModeSelected -> onSignModeSelected()
is LoginViewEvents.OnLoginFlowRetrieved -> is LoginViewEvents.OnLoginFlowRetrieved ->
addFragmentToBackstack(R.id.loginFragmentContainer, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginSignUpSignInSelectionFragment::class.java, if (loginViewEvents.isSso) {
LoginSignUpSignInSsoFragment::class.java
} else {
LoginSignUpSignInSelectionFragment::class.java
},
option = commonOption) option = commonOption)
is LoginViewEvents.OnWebLoginError -> onWebLoginError(loginViewEvents) is LoginViewEvents.OnWebLoginError -> onWebLoginError(loginViewEvents)
is LoginViewEvents.OnForgetPasswordClicked -> is LoginViewEvents.OnForgetPasswordClicked ->
@ -239,16 +244,14 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
SignMode.SignIn -> { SignMode.SignIn -> {
// It depends on the LoginMode // It depends on the LoginMode
when (state.loginMode) { when (state.loginMode) {
LoginMode.Unknown -> error("Developer error") LoginMode.Unknown,
LoginMode.Sso -> error("Developer error")
LoginMode.Password -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginMode.Password -> addFragmentToBackstack(R.id.loginFragmentContainer,
LoginFragment::class.java, LoginFragment::class.java,
tag = FRAGMENT_LOGIN_TAG, tag = FRAGMENT_LOGIN_TAG,
option = commonOption) option = commonOption)
LoginMode.Sso -> addFragmentToBackstack(R.id.loginFragmentContainer,
LoginWebFragment::class.java,
option = commonOption)
LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes) LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes)
} }.exhaustive
} }
SignMode.SignInWithMatrixId -> addFragmentToBackstack(R.id.loginFragmentContainer, SignMode.SignInWithMatrixId -> addFragmentToBackstack(R.id.loginFragmentContainer,
LoginFragment::class.java, LoginFragment::class.java,
@ -257,6 +260,17 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
}.exhaustive }.exhaustive
} }
/**
* Handle the SSO redirection here
*/
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.data
?.let { tryThis { it.getQueryParameter("loginToken") } }
?.let { loginViewModel.handle(LoginAction.LoginWithToken(it)) }
}
private fun onRegistrationStageNotSupported() { private fun onRegistrationStageNotSupported() {
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle(R.string.app_name) .setTitle(R.string.app_name)

View file

@ -21,7 +21,7 @@ import android.view.View
import butterknife.OnClick import butterknife.OnClick
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.utils.openUrlInExternalBrowser import im.vector.riotx.core.utils.openUrlInChromeCustomTab
import kotlinx.android.synthetic.main.fragment_login_server_selection.* import kotlinx.android.synthetic.main.fragment_login_server_selection.*
import me.gujun.android.span.span import me.gujun.android.span.span
import javax.inject.Inject import javax.inject.Inject
@ -55,8 +55,8 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
} }
@OnClick(R.id.loginServerChoiceModularLearnMore) @OnClick(R.id.loginServerChoiceModularLearnMore)
fun learMore() { fun learnMore() {
openUrlInExternalBrowser(requireActivity(), MODULAR_LINK) openUrlInChromeCustomTab(requireActivity(), null, MODULAR_LINK)
} }
@OnClick(R.id.loginServerChoiceMatrixOrg) @OnClick(R.id.loginServerChoiceMatrixOrg)
@ -113,7 +113,7 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
if (state.loginMode != LoginMode.Unknown) { if (state.loginMode != LoginMode.Unknown) {
// LoginFlow for matrix.org has been retrieved // LoginFlow for matrix.org has been retrieved
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved)) loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved(state.loginMode == LoginMode.Sso)))
} }
} }
} }

View file

@ -26,7 +26,7 @@ import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.utils.ensureProtocol import im.vector.riotx.core.utils.ensureProtocol
import im.vector.riotx.core.utils.openUrlInExternalBrowser import im.vector.riotx.core.utils.openUrlInChromeCustomTab
import kotlinx.android.synthetic.main.fragment_login_server_url_form.* import kotlinx.android.synthetic.main.fragment_login_server_url_form.*
import javax.inject.Inject import javax.inject.Inject
@ -84,7 +84,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
@OnClick(R.id.loginServerUrlFormLearnMore) @OnClick(R.id.loginServerUrlFormLearnMore)
fun learnMore() { fun learnMore() {
openUrlInExternalBrowser(requireActivity(), MODULAR_LINK) openUrlInChromeCustomTab(requireActivity(), null, MODULAR_LINK)
} }
override fun resetViewModel() { override fun resetViewModel() {
@ -124,7 +124,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
if (state.loginMode != LoginMode.Unknown) { if (state.loginMode != LoginMode.Unknown) {
// The home server url is valid // The home server url is valid
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved)) loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnLoginFlowRetrieved(state.loginMode == LoginMode.Sso)))
} }
} }
} }

View file

@ -26,13 +26,11 @@ import javax.inject.Inject
/** /**
* In this screen, the user is asked to sign up or to sign in to the homeserver * In this screen, the user is asked to sign up or to sign in to the homeserver
*/ */
class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFragment() { open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFragment() {
override fun getLayoutResId() = R.layout.fragment_login_signup_signin_selection override fun getLayoutResId() = R.layout.fragment_login_signup_signin_selection
private var isSsoSignIn: Boolean = false protected fun setupUi(state: LoginViewState) {
private fun setupUi(state: LoginViewState) {
when (state.serverType) { when (state.serverType) {
ServerType.MatrixOrg -> { ServerType.MatrixOrg -> {
loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org) loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
@ -54,25 +52,14 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr
} }
} }
private fun setupButtons(state: LoginViewState) { private fun setupButtons() {
isSsoSignIn = state.loginMode == LoginMode.Sso loginSignupSigninSubmit.text = getString(R.string.login_signup)
loginSignupSigninSignIn.isVisible = true
if (isSsoSignIn) {
loginSignupSigninSubmit.text = getString(R.string.login_signin_sso)
loginSignupSigninSignIn.isVisible = false
} else {
loginSignupSigninSubmit.text = getString(R.string.login_signup)
loginSignupSigninSignIn.isVisible = true
}
} }
@OnClick(R.id.loginSignupSigninSubmit) @OnClick(R.id.loginSignupSigninSubmit)
fun signUp() { open fun submit() {
if (isSsoSignIn) { loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp))
signIn()
} else {
loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp))
}
} }
@OnClick(R.id.loginSignupSigninSignIn) @OnClick(R.id.loginSignupSigninSignIn)
@ -86,6 +73,6 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr
override fun updateWithState(state: LoginViewState) { override fun updateWithState(state: LoginViewState) {
setupUi(state) setupUi(state)
setupButtons(state) setupButtons()
} }
} }

View file

@ -0,0 +1,99 @@
/*
* 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.riotx.features.login
import android.content.ComponentName
import android.net.Uri
import androidx.browser.customtabs.CustomTabsClient
import androidx.browser.customtabs.CustomTabsServiceConnection
import androidx.browser.customtabs.CustomTabsSession
import androidx.core.view.isVisible
import im.vector.riotx.R
import im.vector.riotx.core.utils.openUrlInChromeCustomTab
import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.*
import javax.inject.Inject
/**
* In this screen, the user is asked to sign up or to sign in using SSO
* This Fragment binds a CustomTabsServiceConnection if available, then prefetch the SSO url, as it will be likely to be opened.
*/
open class LoginSignUpSignInSsoFragment @Inject constructor() : LoginSignUpSignInSelectionFragment() {
private var ssoUrl: String? = null
private var customTabsServiceConnection: CustomTabsServiceConnection? = null
private var customTabsClient: CustomTabsClient? = null
private var customTabsSession: CustomTabsSession? = null
override fun onStart() {
super.onStart()
val packageName = CustomTabsClient.getPackageName(requireContext(), null)
// packageName can be null if there are 0 or several CustomTabs compatible browsers installed on the device
if (packageName != null) {
customTabsServiceConnection = object : CustomTabsServiceConnection() {
override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
customTabsClient = client
.also { it.warmup(0L) }
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
.also {
CustomTabsClient.bindCustomTabsService(
requireContext(),
// Despite the API, packageName cannot be null
packageName,
it
)
}
}
}
private fun prefetchUrl(url: String) {
if (ssoUrl != null) return
ssoUrl = url
if (customTabsSession == null) {
customTabsSession = customTabsClient?.newSession(null)
}
customTabsSession?.mayLaunchUrl(Uri.parse(url), null, null)
}
override fun onStop() {
super.onStop()
customTabsServiceConnection?.let { requireContext().unbindService(it) }
customTabsServiceConnection = null
}
private fun setupButtons() {
loginSignupSigninSubmit.text = getString(R.string.login_signin_sso)
loginSignupSigninSignIn.isVisible = false
}
override fun submit() {
ssoUrl?.let { openUrlInChromeCustomTab(requireContext(), customTabsSession, it) }
}
override fun updateWithState(state: LoginViewState) {
setupUi(state)
setupButtons()
prefetchUrl(state.getSsoUrl())
}
}

View file

@ -34,7 +34,7 @@ sealed class LoginViewEvents : VectorViewEvents {
object OpenServerSelection : LoginViewEvents() object OpenServerSelection : LoginViewEvents()
object OnServerSelectionDone : LoginViewEvents() object OnServerSelectionDone : LoginViewEvents()
object OnLoginFlowRetrieved : LoginViewEvents() data class OnLoginFlowRetrieved(val isSso: Boolean) : LoginViewEvents()
object OnSignModeSelected : LoginViewEvents() object OnSignModeSelected : LoginViewEvents()
object OnForgetPasswordClicked : LoginViewEvents() object OnForgetPasswordClicked : LoginViewEvents()
object OnResetPasswordSendThreePidDone : LoginViewEvents() object OnResetPasswordSendThreePidDone : LoginViewEvents()

View file

@ -32,6 +32,7 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.api.auth.AuthenticationService
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.LoginFlowTypes
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.FlowResult import im.vector.matrix.android.api.auth.registration.FlowResult
import im.vector.matrix.android.api.auth.registration.RegistrationResult import im.vector.matrix.android.api.auth.registration.RegistrationResult
@ -40,7 +41,6 @@ import im.vector.matrix.android.api.auth.registration.Stage
import im.vector.matrix.android.api.auth.wellknown.WellknownResult import im.vector.matrix.android.api.auth.wellknown.WellknownResult
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
@ -110,6 +110,7 @@ class LoginViewModel @AssistedInject constructor(
is LoginAction.InitWith -> handleInitWith(action) is LoginAction.InitWith -> handleInitWith(action)
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action) is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action)
is LoginAction.LoginOrRegister -> handleLoginOrRegister(action) is LoginAction.LoginOrRegister -> handleLoginOrRegister(action)
is LoginAction.LoginWithToken -> handleLoginWithToken(action)
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action) is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
is LoginAction.ResetPassword -> handleResetPassword(action) is LoginAction.ResetPassword -> handleResetPassword(action)
is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed() is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
@ -120,6 +121,41 @@ class LoginViewModel @AssistedInject constructor(
}.exhaustive }.exhaustive
} }
private fun handleLoginWithToken(action: LoginAction.LoginWithToken) {
val safeLoginWizard = loginWizard
if (safeLoginWizard == null) {
setState {
copy(
asyncLoginAction = Fail(Throwable("Bad configuration"))
)
}
} else {
setState {
copy(
asyncLoginAction = Loading()
)
}
currentTask = safeLoginWizard.loginWithToken(
action.loginToken,
object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
onSessionCreated(data)
}
override fun onFailure(failure: Throwable) {
_viewEvents.post(LoginViewEvents.Failure(failure))
setState {
copy(
asyncLoginAction = Fail(failure)
)
}
}
})
}
}
private fun handleSetupSsoForSessionRecovery(action: LoginAction.SetupSsoForSessionRecovery) { private fun handleSetupSsoForSessionRecovery(action: LoginAction.SetupSsoForSessionRecovery) {
setState { setState {
copy( copy(
@ -635,9 +671,9 @@ class LoginViewModel @AssistedInject constructor(
is LoginFlowResult.Success -> { is LoginFlowResult.Success -> {
val loginMode = when { val loginMode = when {
// SSO login is taken first // SSO login is taken first
data.loginFlowResponse.flows.any { it.type == LoginFlowTypes.SSO } -> LoginMode.Sso data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso
data.loginFlowResponse.flows.any { it.type == LoginFlowTypes.PASSWORD } -> LoginMode.Password data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported else -> LoginMode.Unsupported
} }
if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) { if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) {
@ -648,7 +684,7 @@ class LoginViewModel @AssistedInject constructor(
asyncHomeServerLoginFlowRequest = Uninitialized, asyncHomeServerLoginFlowRequest = Uninitialized,
homeServerUrl = data.homeServerUrl, homeServerUrl = data.homeServerUrl,
loginMode = loginMode, loginMode = loginMode,
loginModeSupportedTypes = data.loginFlowResponse.flows.mapNotNull { it.type }.toList() loginModeSupportedTypes = data.supportedLoginTypes.toList()
) )
} }
} }

View file

@ -22,6 +22,9 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.PersistState import com.airbnb.mvrx.PersistState
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.auth.SSO_REDIRECT_PATH
import im.vector.matrix.android.api.auth.SSO_REDIRECT_URL_PARAM
import im.vector.riotx.core.extensions.appendParamToUrl
data class LoginViewState( data class LoginViewState(
val asyncLoginAction: Async<Unit> = Uninitialized, val asyncLoginAction: Async<Unit> = Uninitialized,
@ -64,4 +67,22 @@ data class LoginViewState(
fun isUserLogged(): Boolean { fun isUserLogged(): Boolean {
return asyncLoginAction is Success return asyncLoginAction is Success
} }
fun getSsoUrl(): String {
return buildString {
append(homeServerUrl?.trim { it == '/' })
append(SSO_REDIRECT_PATH)
// Set a redirect url we will intercept later
appendParamToUrl(SSO_REDIRECT_URL_PARAM, RIOTX_REDIRECT_URL)
deviceId?.takeIf { it.isNotBlank() }?.let {
// But https://github.com/matrix-org/synapse/issues/5755
appendParamToUrl("device_id", it)
}
}
}
companion object {
// Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string
private const val RIOTX_REDIRECT_URL = "riotx://riotx"
}
} }

View file

@ -33,8 +33,6 @@ import androidx.appcompat.app.AlertDialog
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import im.vector.matrix.android.api.auth.LOGIN_FALLBACK_PATH import im.vector.matrix.android.api.auth.LOGIN_FALLBACK_PATH
import im.vector.matrix.android.api.auth.REGISTER_FALLBACK_PATH import im.vector.matrix.android.api.auth.REGISTER_FALLBACK_PATH
import im.vector.matrix.android.api.auth.SSO_FALLBACK_PATH
import im.vector.matrix.android.api.auth.SSO_REDIRECT_URL_PARAM
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.riotx.R import im.vector.riotx.R
@ -48,7 +46,7 @@ import java.net.URLDecoder
import javax.inject.Inject import javax.inject.Inject
/** /**
* This screen is displayed for SSO login and also when the application does not support login flow or registration flow * This screen is displayed when the application does not support login flow or registration flow
* of the homeserver, as a fallback to login or to create an account * of the homeserver, as a fallback to login or to create an account
*/ */
class LoginWebFragment @Inject constructor( class LoginWebFragment @Inject constructor(
@ -128,17 +126,7 @@ class LoginWebFragment @Inject constructor(
val url = buildString { val url = buildString {
append(state.homeServerUrl?.trim { it == '/' }) append(state.homeServerUrl?.trim { it == '/' })
if (state.signMode == SignMode.SignIn) { if (state.signMode == SignMode.SignIn) {
if (state.loginMode == LoginMode.Sso) { append(LOGIN_FALLBACK_PATH)
append(SSO_FALLBACK_PATH)
// We do not want to deal with the result, so let the fallback login page to handle it for us
appendParamToUrl(SSO_REDIRECT_URL_PARAM,
buildString {
append(state.homeServerUrl?.trim { it == '/' })
append(LOGIN_FALLBACK_PATH)
})
} else {
append(LOGIN_FALLBACK_PATH)
}
state.deviceId?.takeIf { it.isNotBlank() }?.let { state.deviceId?.takeIf { it.isNotBlank() }?.let {
// But https://github.com/matrix-org/synapse/issues/5755 // But https://github.com/matrix-org/synapse/issues/5755
appendParamToUrl("device_id", it) appendParamToUrl("device_id", it)
@ -226,7 +214,9 @@ class LoginWebFragment @Inject constructor(
* @return * @return
*/ */
override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean { override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean {
if (null != url && url.startsWith("js:")) { if (url == null) return super.shouldOverrideUrlLoading(view, url as String?)
if (url.startsWith("js:")) {
var json = url.substring(3) var json = url.substring(3)
var javascriptResponse: JavascriptResponse? = null var javascriptResponse: JavascriptResponse? = null

View file

@ -22,10 +22,10 @@ import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
import im.vector.matrix.android.api.failure.toRegistrationFlowResponse import im.vector.matrix.android.api.failure.toRegistrationFlowResponse
import im.vector.matrix.android.api.session.Session 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.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.crypto.crosssigning.isVerified import im.vector.matrix.android.internal.crypto.crosssigning.isVerified
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx

View file

@ -30,13 +30,13 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.NoOpMatrixCallback import im.vector.matrix.android.api.NoOpMatrixCallback
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
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.session.crypto.verification.VerificationMethod import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
import im.vector.matrix.android.api.session.crypto.verification.VerificationService import im.vector.matrix.android.api.session.crypto.verification.VerificationService
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo

View file

@ -28,9 +28,9 @@ import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.api.auth.AuthenticationService
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.LoginFlowTypes
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.internal.auth.data.LoginFlowTypes
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.extensions.hasUnsavedKeys import im.vector.riotx.core.extensions.hasUnsavedKeys
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
@ -105,9 +105,9 @@ class SoftLogoutViewModel @AssistedInject constructor(
is LoginFlowResult.Success -> { is LoginFlowResult.Success -> {
val loginMode = when { val loginMode = when {
// SSO login is taken first // SSO login is taken first
data.loginFlowResponse.flows.any { it.type == LoginFlowTypes.SSO } -> LoginMode.Sso data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso
data.loginFlowResponse.flows.any { it.type == LoginFlowTypes.PASSWORD } -> LoginMode.Password data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported else -> LoginMode.Unsupported
} }
if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) { if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) {

View file

@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:pathData="M20,12H4"
android:strokeWidth="2"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"
android:strokeLineJoin="round"
tools:strokeColor="#00F000" />
<path
android:fillColor="#00000000"
android:pathData="M10,18L4,12L10,6"
android:strokeWidth="2"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"
android:strokeLineJoin="round"
tools:strokeColor="#00F000" />
</vector>