Identity: ping API V2 and cleanup

This commit is contained in:
Benoit Marty 2020-05-11 01:33:11 +02:00
parent 38fb7185b6
commit ed2f62cbe7
13 changed files with 125 additions and 21 deletions

View File

@ -31,6 +31,13 @@ interface IdentityService {
fun getCurrentIdentityServer(): String? fun getCurrentIdentityServer(): String?
/**
* Check if the identity server is valid
* See https://matrix.org/docs/spec/identity_service/latest#status-check
* RiotX SDK only supports identity server API v2
*/
fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable
/** /**
* Update the identity server url. * Update the identity server url.
* @param url the new url. Set to null to disconnect from the identity server * @param url the new url. Set to null to disconnect from the identity server

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.identity package im.vector.matrix.android.api.session.identity
sealed class IdentityServiceError(cause: Throwable? = null) : Throwable(cause = cause) { sealed class IdentityServiceError(cause: Throwable? = null) : Throwable(cause = cause) {
object OutdatedIdentityServer : IdentityServiceError(null)
object NoIdentityServerConfigured : IdentityServiceError(null) object NoIdentityServerConfigured : IdentityServiceError(null)
object TermsNotSignedException : IdentityServiceError(null) object TermsNotSignedException : IdentityServiceError(null)
object BulkLookupSha256NotSupported : IdentityServiceError(null) object BulkLookupSha256NotSupported : IdentityServiceError(null)

View File

@ -29,7 +29,9 @@ internal object NetworkConstants {
// Identity server // Identity server
const val URI_IDENTITY_PATH = "_matrix/identity/api/v1/" const val URI_IDENTITY_PATH = "_matrix/identity/api/v1/"
const val URI_IDENTITY_PATH_V2 = "_matrix/identity/v2/"
const val URI_IDENTITY_PREFIX_PATH = "_matrix/identity/v2"
const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/"
const val URI_API_PREFIX_IDENTITY = "_matrix/identity/api/v1" const val URI_API_PREFIX_IDENTITY = "_matrix/identity/api/v1"

View File

@ -61,6 +61,7 @@ internal class DefaultIdentityService @Inject constructor(
private val getOpenIdTokenTask: GetOpenIdTokenTask, private val getOpenIdTokenTask: GetOpenIdTokenTask,
private val bulkLookupTask: BulkLookupTask, private val bulkLookupTask: BulkLookupTask,
private val identityRegisterTask: IdentityRegisterTask, private val identityRegisterTask: IdentityRegisterTask,
private val identityPingTask: IdentityPingTask,
private val identityDisconnectTask: IdentityDisconnectTask, private val identityDisconnectTask: IdentityDisconnectTask,
private val identityRequestTokenForBindingTask: IdentityRequestTokenForBindingTask, private val identityRequestTokenForBindingTask: IdentityRequestTokenForBindingTask,
@Unauthenticated @Unauthenticated
@ -157,6 +158,14 @@ internal class DefaultIdentityService @Inject constructor(
} }
} }
override fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable {
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
identityPingTask.execute(IdentityPingTask.Params(api))
}
}
override fun setNewIdentityServer(url: String?, callback: MatrixCallback<String?>): Cancelable { override fun setNewIdentityServer(url: String?, callback: MatrixCallback<String?>): Cancelable {
val urlCandidate = url?.let { param -> val urlCandidate = url?.let { param ->
buildString { buildString {

View File

@ -32,14 +32,21 @@ internal interface IdentityAuthAPI {
/** /**
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
* Simple ping call to check if server alive * Simple ping call to check if server exists and is alive
* *
* Ref: https://matrix.org/docs/spec/identity_service/unstable#status-check * Ref: https://matrix.org/docs/spec/identity_service/unstable#status-check
* https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2
* *
* @return 200 in case of success * @return 200 in case of success
*/ */
@GET(NetworkConstants.URI_API_PREFIX_IDENTITY) @GET(NetworkConstants.URI_IDENTITY_PREFIX_PATH)
fun ping(): Call<Void> fun ping(): Call<Unit>
/**
* Ping v1 will be used to check outdated Identity server
*/
@GET("_matrix/identity/api/v1")
fun pingV1(): Call<Unit>
/** /**
* Exchanges an OpenID token from the homeserver for an access token to access the identity server. * Exchanges an OpenID token from the homeserver for an access token to access the identity server.

View File

@ -92,6 +92,9 @@ internal abstract class IdentityModule {
@Binds @Binds
abstract fun bindIdentityServiceStore(store: RealmIdentityServiceStore): IdentityServiceStore abstract fun bindIdentityServiceStore(store: RealmIdentityServiceStore): IdentityServiceStore
@Binds
abstract fun bindIdentityPingTask(task: DefaultIdentityPingTask): IdentityPingTask
@Binds @Binds
abstract fun bindIdentityRegisterTask(task: DefaultIdentityRegisterTask): IdentityRegisterTask abstract fun bindIdentityRegisterTask(task: DefaultIdentityRegisterTask): IdentityRegisterTask

View File

@ -0,0 +1,52 @@
/*
* 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.session.identity
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.identity.IdentityServiceError
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
internal interface IdentityPingTask : Task<IdentityPingTask.Params, Unit> {
data class Params(
val identityAuthAPI: IdentityAuthAPI
)
}
internal class DefaultIdentityPingTask @Inject constructor() : IdentityPingTask {
override suspend fun execute(params: IdentityPingTask.Params) {
try {
executeRequest<Unit>(null) {
apiCall = params.identityAuthAPI.ping()
}
} catch (throwable: Throwable) {
if (throwable is Failure.ServerError && throwable.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// Check if API v1 is available
executeRequest<Unit>(null) {
apiCall = params.identityAuthAPI.pingV1()
}
// API V1 is responding, but not V2 -> Outdated
throw IdentityServiceError.OutdatedIdentityServer
} else {
throw throwable
}
}
}
}

View File

@ -198,7 +198,6 @@ class DiscoverySettingsController @Inject constructor(
is Loading -> is Loading ->
settingsProgressItem { settingsProgressItem {
id("progress${pidInfo.threePid.value}") id("progress${pidInfo.threePid.value}")
} }
is Success -> Unit /* Cannot happen */ is Success -> Unit /* Cannot happen */
} }

View File

@ -19,6 +19,6 @@ package im.vector.riotx.features.discovery.change
import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.core.platform.VectorViewModelAction
sealed class SetIdentityServerAction : VectorViewModelAction { sealed class SetIdentityServerAction : VectorViewModelAction {
data class UpdateServerName(val url: String) : SetIdentityServerAction() data class UpdateIdentityServerUrl(val url: String) : SetIdentityServerAction()
object DoChangeServerName : SetIdentityServerAction() object DoChangeIdentityServerUrl : SetIdentityServerAction()
} }

View File

@ -68,7 +68,7 @@ class SetIdentityServerFragment @Inject constructor(
mKeyTextEdit.isEnabled = true mKeyTextEdit.isEnabled = true
mProgressBar.isVisible = false mProgressBar.isVisible = false
} }
val newText = state.newIdentityServer ?: "" val newText = state.newIdentityServerUrl ?: ""
if (newText != mKeyTextEdit.text.toString()) { if (newText != mKeyTextEdit.text.toString()) {
mKeyTextEdit.setText(newText) mKeyTextEdit.setText(newText)
} }
@ -80,7 +80,7 @@ class SetIdentityServerFragment @Inject constructor(
R.id.action_submit -> { R.id.action_submit -> {
withState(viewModel) { state -> withState(viewModel) { state ->
if (!state.isVerifyingServer) { if (!state.isVerifyingServer) {
viewModel.handle(SetIdentityServerAction.DoChangeServerName) viewModel.handle(SetIdentityServerAction.DoChangeIdentityServerUrl)
} }
} }
true true
@ -98,7 +98,7 @@ class SetIdentityServerFragment @Inject constructor(
if (actionId == EditorInfo.IME_ACTION_DONE) { if (actionId == EditorInfo.IME_ACTION_DONE) {
withState(viewModel) { state -> withState(viewModel) { state ->
if (!state.isVerifyingServer) { if (!state.isVerifyingServer) {
viewModel.handle(SetIdentityServerAction.DoChangeServerName) viewModel.handle(SetIdentityServerAction.DoChangeIdentityServerUrl)
} }
} }
return@setOnEditorActionListener true return@setOnEditorActionListener true
@ -147,8 +147,8 @@ class SetIdentityServerFragment @Inject constructor(
private fun processIdentityServerChange() { private fun processIdentityServerChange() {
withState(viewModel) { state -> withState(viewModel) { state ->
if (state.newIdentityServer != null) { if (state.newIdentityServerUrl != null) {
sharedViewModel.requestChangeToIdentityServer(state.newIdentityServer) sharedViewModel.requestChangeToIdentityServer(state.newIdentityServerUrl)
parentFragmentManager.popBackStack() parentFragmentManager.popBackStack()
} }
} }
@ -156,7 +156,7 @@ class SetIdentityServerFragment @Inject constructor(
@OnTextChanged(R.id.discovery_identity_server_enter_edittext) @OnTextChanged(R.id.discovery_identity_server_enter_edittext)
fun onTextEditChange(s: Editable?) { fun onTextEditChange(s: Editable?) {
s?.toString()?.let { viewModel.handle(SetIdentityServerAction.UpdateServerName(it)) } s?.toString()?.let { viewModel.handle(SetIdentityServerAction.UpdateIdentityServerUrl(it)) }
} }
override fun onResume() { override fun onResume() {

View File

@ -20,8 +20,7 @@ import androidx.annotation.StringRes
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
data class SetIdentityServerState( data class SetIdentityServerState(
val existingIdentityServer: String? = null, val newIdentityServerUrl: String? = null,
val newIdentityServer: String? = null,
@StringRes val errorMessageId: Int? = null, @StringRes val errorMessageId: Int? = null,
val isVerifyingServer: Boolean = false val isVerifyingServer: Boolean = false
) : MvRxState ) : MvRxState

View File

@ -23,6 +23,7 @@ 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.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.identity.IdentityServiceError
import im.vector.matrix.android.api.session.terms.GetTermsResponse import im.vector.matrix.android.api.session.terms.GetTermsResponse
import im.vector.matrix.android.api.session.terms.TermsService import im.vector.matrix.android.api.session.terms.TermsService
import im.vector.riotx.R import im.vector.riotx.R
@ -62,22 +63,22 @@ class SetIdentityServerViewModel @AssistedInject constructor(
override fun handle(action: SetIdentityServerAction) { override fun handle(action: SetIdentityServerAction) {
when (action) { when (action) {
is SetIdentityServerAction.UpdateServerName -> updateServerName(action) is SetIdentityServerAction.UpdateIdentityServerUrl -> updateIdentityServerUrl(action)
SetIdentityServerAction.DoChangeServerName -> doChangeServerName() SetIdentityServerAction.DoChangeIdentityServerUrl -> doChangeIdentityServerUrl()
}.exhaustive }.exhaustive
} }
private fun updateServerName(action: SetIdentityServerAction.UpdateServerName) { private fun updateIdentityServerUrl(action: SetIdentityServerAction.UpdateIdentityServerUrl) {
setState { setState {
copy( copy(
newIdentityServer = action.url, newIdentityServerUrl = action.url,
errorMessageId = null errorMessageId = null
) )
} }
} }
private fun doChangeServerName() = withState { private fun doChangeIdentityServerUrl() = withState {
var baseUrl: String? = it.newIdentityServer var baseUrl: String? = it.newIdentityServerUrl
if (baseUrl.isNullOrBlank()) { if (baseUrl.isNullOrBlank()) {
setState { setState {
copy(errorMessageId = R.string.settings_discovery_please_enter_server) copy(errorMessageId = R.string.settings_discovery_please_enter_server)
@ -89,6 +90,29 @@ class SetIdentityServerViewModel @AssistedInject constructor(
copy(isVerifyingServer = true) copy(isVerifyingServer = true)
} }
// First ping the identity server v2 API
mxSession.identityService().isValidIdentityServer(baseUrl, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// Ok, next step
checkTerms(baseUrl)
}
override fun onFailure(failure: Throwable) {
setState {
copy(
isVerifyingServer = false,
errorMessageId = if (failure is IdentityServiceError.OutdatedIdentityServer) {
R.string.settings_discovery_outdated_identity_server
} else {
R.string.settings_discovery_bad_identity_server
}
)
}
}
})
}
private fun checkTerms(baseUrl: String) {
mxSession.getTerms(TermsService.ServiceType.IdentityService, mxSession.getTerms(TermsService.ServiceType.IdentityService,
baseUrl, baseUrl,
object : MatrixCallback<GetTermsResponse> { object : MatrixCallback<GetTermsResponse> {

View File

@ -1738,6 +1738,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
<string name="settings_discovery_enter_identity_server">Enter a new identity server</string> <string name="settings_discovery_enter_identity_server">Enter a new identity server</string>
<string name="settings_discovery_bad_identity_server">Could not connect to identity server</string> <string name="settings_discovery_bad_identity_server">Could not connect to identity server</string>
<string name="settings_discovery_outdated_identity_server">This identity server does not support API V2</string>
<string name="settings_discovery_please_enter_server">Please enter the identity server url</string> <string name="settings_discovery_please_enter_server">Please enter the identity server url</string>
<string name="settings_discovery_no_terms_title">Identity server has no terms of services</string> <string name="settings_discovery_no_terms_title">Identity server has no terms of services</string>
<string name="settings_discovery_no_terms">The identity server you have chosen does not have any terms of services. Only continue if you trust the owner of the service</string> <string name="settings_discovery_no_terms">The identity server you have chosen does not have any terms of services. Only continue if you trust the owner of the service</string>