Merge pull request #749 from vector-im/feature/hs_discovery
Support entering a RiotWeb client URL instead of the homeserver URL
This commit is contained in:
commit
41d1b77370
@ -5,7 +5,7 @@ Features ✨:
|
|||||||
- Breadcrumbs: switch from one room to another quickly (#571)
|
- Breadcrumbs: switch from one room to another quickly (#571)
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
-
|
- Support entering a RiotWeb client URL instead of the homeserver URL during connection (#744)
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
-
|
-
|
||||||
|
@ -22,7 +22,8 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
|||||||
sealed class LoginFlowResult {
|
sealed class LoginFlowResult {
|
||||||
data class Success(
|
data class Success(
|
||||||
val loginFlowResponse: LoginFlowResponse,
|
val loginFlowResponse: LoginFlowResponse,
|
||||||
val isLoginAndRegistrationSupported: Boolean
|
val isLoginAndRegistrationSupported: Boolean,
|
||||||
|
val homeServerUrl: String
|
||||||
) : LoginFlowResult()
|
) : LoginFlowResult()
|
||||||
|
|
||||||
object OutdatedHomeserver : LoginFlowResult()
|
object OutdatedHomeserver : LoginFlowResult()
|
||||||
|
@ -36,7 +36,7 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
|||||||
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
||||||
object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false")))
|
object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false")))
|
||||||
// When server send an error, but it cannot be interpreted as a MatrixError
|
// When server send an error, but it cannot be interpreted as a MatrixError
|
||||||
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody))
|
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException("HTTP $httpCode: $errorBody"))
|
||||||
|
|
||||||
data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString()))
|
data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString()))
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.api.auth.data.Credentials
|
|||||||
import im.vector.matrix.android.api.auth.data.Versions
|
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.login.ResetPasswordMailConfirmed
|
import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed
|
||||||
import im.vector.matrix.android.internal.auth.registration.*
|
import im.vector.matrix.android.internal.auth.registration.*
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
@ -31,6 +32,12 @@ import retrofit2.http.*
|
|||||||
*/
|
*/
|
||||||
internal interface AuthAPI {
|
internal interface AuthAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Riot config file
|
||||||
|
*/
|
||||||
|
@GET("config.json")
|
||||||
|
fun getRiotConfig(): Call<RiotConfig>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the version information of the homeserver
|
* Get the version information of the homeserver
|
||||||
*/
|
*/
|
||||||
|
@ -16,16 +16,19 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.auth
|
package im.vector.matrix.android.internal.auth
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
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.AuthenticationService
|
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||||
import im.vector.matrix.android.api.auth.data.*
|
import im.vector.matrix.android.api.auth.data.*
|
||||||
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.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.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.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.registration.DefaultRegistrationWizard
|
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
|
||||||
@ -40,6 +43,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
||||||
private val okHttpClient: Lazy<OkHttpClient>,
|
private val okHttpClient: Lazy<OkHttpClient>,
|
||||||
@ -84,7 +88,12 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
|||||||
{
|
{
|
||||||
if (it is LoginFlowResult.Success) {
|
if (it is LoginFlowResult.Success) {
|
||||||
// The homeserver exists and up to date, keep the config
|
// The homeserver exists and up to date, keep the config
|
||||||
pendingSessionData = PendingSessionData(homeServerConnectionConfig)
|
// Homeserver url may have been changed, if it was a Riot url
|
||||||
|
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||||
|
homeServerUri = Uri.parse(it.homeServerUrl)
|
||||||
|
)
|
||||||
|
|
||||||
|
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
|
||||||
.also { data -> pendingSessionStore.savePendingSessionData(data) }
|
.also { data -> pendingSessionStore.savePendingSessionData(data) }
|
||||||
}
|
}
|
||||||
callback.onSuccess(it)
|
callback.onSuccess(it)
|
||||||
@ -97,20 +106,71 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
|
|||||||
.toCancelable()
|
.toCancelable()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig) = withContext(coroutineDispatchers.io) {
|
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||||
|
return withContext(coroutineDispatchers.io) {
|
||||||
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||||
|
|
||||||
// First check the homeserver version
|
// First check the homeserver version
|
||||||
val versions = executeRequest<Versions> {
|
runCatching {
|
||||||
|
executeRequest<Versions> {
|
||||||
apiCall = authAPI.versions()
|
apiCall = authAPI.versions()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.map { versions ->
|
||||||
|
// Ok, it seems that the homeserver url is valid
|
||||||
|
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
|
||||||
|
}
|
||||||
|
.fold(
|
||||||
|
{
|
||||||
|
it
|
||||||
|
},
|
||||||
|
{
|
||||||
|
if (it is Failure.OtherServerError
|
||||||
|
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
|
||||||
|
// It's maybe a Riot url?
|
||||||
|
getRiotLoginFlowInternal(homeServerConnectionConfig)
|
||||||
|
} else {
|
||||||
|
throw it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (versions.isSupportedBySdk()) {
|
private suspend fun getRiotLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||||
|
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||||
|
|
||||||
|
// Ok, try to get the config.json file of a RiotWeb client
|
||||||
|
val riotConfig = executeRequest<RiotConfig> {
|
||||||
|
apiCall = authAPI.getRiotConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (riotConfig.defaultHomeServerUrl?.isNotBlank() == true) {
|
||||||
|
// Ok, good sign, we got a default hs url
|
||||||
|
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||||
|
homeServerUri = Uri.parse(riotConfig.defaultHomeServerUrl)
|
||||||
|
)
|
||||||
|
|
||||||
|
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
|
||||||
|
|
||||||
|
val versions = executeRequest<Versions> {
|
||||||
|
apiCall = newAuthAPI.versions()
|
||||||
|
}
|
||||||
|
|
||||||
|
return getLoginFlowResult(newAuthAPI, versions, riotConfig.defaultHomeServerUrl)
|
||||||
|
} else {
|
||||||
|
// Config exists, but there is no default homeserver url (ex: https://riot.im/app)
|
||||||
|
throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
|
||||||
|
return if (versions.isSupportedBySdk()) {
|
||||||
// Get the login flow
|
// Get the login flow
|
||||||
val loginFlowResponse = executeRequest<LoginFlowResponse> {
|
val loginFlowResponse = executeRequest<LoginFlowResponse> {
|
||||||
apiCall = authAPI.getLoginFlows()
|
apiCall = authAPI.getLoginFlows()
|
||||||
}
|
}
|
||||||
LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk())
|
LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
|
||||||
} else {
|
} else {
|
||||||
// Not supported
|
// Not supported
|
||||||
LoginFlowResult.OutdatedHomeserver
|
LoginFlowResult.OutdatedHomeserver
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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.auth.data
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class RiotConfig(
|
||||||
|
// There are plenty of other elements in the file config.json of a RiotWeb client, but for the moment only one is interesting
|
||||||
|
// Ex: "brand", "branding", etc.
|
||||||
|
@Json(name = "default_hs_url")
|
||||||
|
val defaultHomeServerUrl: String?
|
||||||
|
)
|
@ -19,6 +19,7 @@
|
|||||||
package im.vector.matrix.android.internal.network
|
package im.vector.matrix.android.internal.network
|
||||||
|
|
||||||
import com.squareup.moshi.JsonDataException
|
import com.squareup.moshi.JsonDataException
|
||||||
|
import com.squareup.moshi.JsonEncodingException
|
||||||
import im.vector.matrix.android.api.failure.ConsentNotGivenError
|
import im.vector.matrix.android.api.failure.ConsentNotGivenError
|
||||||
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
|
||||||
@ -106,6 +107,9 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure {
|
|||||||
} catch (ex: JsonDataException) {
|
} catch (ex: JsonDataException) {
|
||||||
// This is not a MatrixError
|
// This is not a MatrixError
|
||||||
Timber.w("The error returned by the server is not a MatrixError")
|
Timber.w("The error returned by the server is not a MatrixError")
|
||||||
|
} catch (ex: JsonEncodingException) {
|
||||||
|
// This is not a MatrixError, HTML code?
|
||||||
|
Timber.w("The error returned by the server is not a MatrixError, probably HTML string")
|
||||||
}
|
}
|
||||||
|
|
||||||
return Failure.OtherServerError(errorBodyStr, httpCode)
|
return Failure.OtherServerError(errorBodyStr, httpCode)
|
||||||
|
@ -20,6 +20,7 @@ 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.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import java.net.HttpURLConnection
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -76,6 +77,15 @@ class ErrorFormatter @Inject constructor(private val stringProvider: StringProvi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is Failure.OtherServerError -> {
|
||||||
|
when (throwable.httpCode) {
|
||||||
|
HttpURLConnection.HTTP_NOT_FOUND ->
|
||||||
|
// homeserver not found
|
||||||
|
stringProvider.getString(R.string.login_error_no_homeserver_found)
|
||||||
|
else ->
|
||||||
|
throwable.localizedMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> throwable.localizedMessage
|
else -> throwable.localizedMessage
|
||||||
}
|
}
|
||||||
?: stringProvider.getString(R.string.unknown_error)
|
?: stringProvider.getString(R.string.unknown_error)
|
||||||
|
@ -539,7 +539,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
|||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||||
homeServerUrl = action.homeServerUrl,
|
homeServerUrl = data.homeServerUrl,
|
||||||
loginMode = loginMode,
|
loginMode = loginMode,
|
||||||
loginModeSupportedTypes = data.loginFlowResponse.flows.mapNotNull { it.type }.toList()
|
loginModeSupportedTypes = data.loginFlowResponse.flows.mapNotNull { it.type }.toList()
|
||||||
)
|
)
|
||||||
|
@ -284,6 +284,7 @@
|
|||||||
<string name="login_error_unable_register_mail_ownership">Unable to register : email ownership failure</string>
|
<string name="login_error_unable_register_mail_ownership">Unable to register : email ownership failure</string>
|
||||||
<string name="login_error_invalid_home_server">Please enter a valid URL</string>
|
<string name="login_error_invalid_home_server">Please enter a valid URL</string>
|
||||||
<string name="login_error_unknown_host">This URL is not reachable, please check it</string>
|
<string name="login_error_unknown_host">This URL is not reachable, please check it</string>
|
||||||
|
<string name="login_error_no_homeserver_found">This is not a valid Matrix server address</string>
|
||||||
<string name="login_error_homeserver_not_found">Cannot reach a homeserver at this URL, please check it</string>
|
<string name="login_error_homeserver_not_found">Cannot reach a homeserver at this URL, please check it</string>
|
||||||
<string name="login_error_ssl_handshake">Your device is using an outdated TLS security protocol, vulnerable to attack, for your security you will not be able to connect</string>
|
<string name="login_error_ssl_handshake">Your device is using an outdated TLS security protocol, vulnerable to attack, for your security you will not be able to connect</string>
|
||||||
<string name="login_mobile_device">Mobile</string>
|
<string name="login_mobile_device">Mobile</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user