Support entering a RiotWeb client URL instead of the homeserver URL during connection (#744)

This commit is contained in:
Benoit Marty 2019-12-06 23:46:32 +01:00
parent ff1745b5dc
commit 5f540a5b45
10 changed files with 122 additions and 11 deletions

View File

@ -5,7 +5,7 @@ Features ✨:
- -
Improvements 🙌: Improvements 🙌:
- - Support entering a RiotWeb client URL instead of the homeserver URL during connection (#744)
Other changes: Other changes:
- -

View File

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

View File

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

View File

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

View File

@ -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)
// First check the homeserver version
runCatching {
executeRequest<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
}
}
)
}
}
private suspend fun getRiotLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
val authAPI = buildAuthAPI(homeServerConnectionConfig) val authAPI = buildAuthAPI(homeServerConnectionConfig)
// First check the homeserver version // Ok, try to get the config.json file of a RiotWeb client
val versions = executeRequest<Versions> { val riotConfig = executeRequest<RiotConfig> {
apiCall = authAPI.versions() apiCall = authAPI.getRiotConfig()
} }
if (versions.isSupportedBySdk()) { 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 we cannot retrieve a default homeserver url
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

View File

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

View File

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

View File

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

View File

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

View File

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