diff --git a/CHANGES.md b/CHANGES.md index d6e107299f..4e59c0484f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Improvements 🙌: - - + - Support entering a RiotWeb client URL instead of the homeserver URL during connection (#744) Other changes: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/LoginFlowResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/LoginFlowResult.kt index f0d0c61d58..dd0c93a41c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/LoginFlowResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/LoginFlowResult.kt @@ -22,7 +22,8 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowResponse sealed class LoginFlowResult { data class Success( val loginFlowResponse: LoginFlowResponse, - val isLoginAndRegistrationSupported: Boolean + val isLoginAndRegistrationSupported: Boolean, + val homeServerUrl: String ) : LoginFlowResult() object OutdatedHomeserver : LoginFlowResult() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt index 9d42e8388c..4d44e3346b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt @@ -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())) object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false"))) // 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())) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt index a1c746a299..306a3846bc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt @@ -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.internal.auth.data.LoginFlowResponse 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.registration.* import im.vector.matrix.android.internal.network.NetworkConstants @@ -31,6 +32,12 @@ import retrofit2.http.* */ internal interface AuthAPI { + /** + * Get a Riot config file + */ + @GET("config.json") + fun getRiotConfig(): Call + /** * Get the version information of the homeserver */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt index e7cf999820..b10ed7217f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt @@ -16,16 +16,19 @@ package im.vector.matrix.android.internal.auth +import android.net.Uri import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.api.auth.data.* import im.vector.matrix.android.api.auth.login.LoginWizard 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.util.Cancelable import im.vector.matrix.android.internal.SessionManager 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.login.DefaultLoginWizard import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard @@ -40,6 +43,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import javax.inject.Inject +import javax.net.ssl.HttpsURLConnection internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated private val okHttpClient: Lazy, @@ -84,7 +88,12 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated { if (it is LoginFlowResult.Success) { // 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) } } callback.onSuccess(it) @@ -97,20 +106,71 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated .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 { + 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) - // First check the homeserver version - val versions = executeRequest { - apiCall = authAPI.versions() + // Ok, try to get the config.json file of a RiotWeb client + val riotConfig = executeRequest { + 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 { + 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 val loginFlowResponse = executeRequest { apiCall = authAPI.getLoginFlows() } - LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk()) + LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl) } else { // Not supported LoginFlowResult.OutdatedHomeserver diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/RiotConfig.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/RiotConfig.kt new file mode 100644 index 0000000000..aebcfe0305 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/RiotConfig.kt @@ -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? +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt index 29b20f9739..fa0b9a1f1c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.network 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.Failure import im.vector.matrix.android.api.failure.MatrixError @@ -106,6 +107,9 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure { } catch (ex: JsonDataException) { // This 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) diff --git a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt index 621031f166..6f34415d9a 100644 --- a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError import im.vector.riotx.R import im.vector.riotx.core.resources.StringProvider +import java.net.HttpURLConnection import java.net.SocketTimeoutException import java.net.UnknownHostException 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 } ?: stringProvider.getString(R.string.unknown_error) diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index de76f6b416..00207cbfbf 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -539,7 +539,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi setState { copy( asyncHomeServerLoginFlowRequest = Uninitialized, - homeServerUrl = action.homeServerUrl, + homeServerUrl = data.homeServerUrl, loginMode = loginMode, loginModeSupportedTypes = data.loginFlowResponse.flows.mapNotNull { it.type }.toList() ) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 57e9ca35d6..2e4d04354b 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -284,6 +284,7 @@ Unable to register : email ownership failure Please enter a valid URL This URL is not reachable, please check it + This is not a valid Matrix server address Cannot reach a homeserver at this URL, please check it Your device is using an outdated TLS security protocol, vulnerable to attack, for your security you will not be able to connect Mobile