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?
/**
* 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.
* @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
sealed class IdentityServiceError(cause: Throwable? = null) : Throwable(cause = cause) {
object OutdatedIdentityServer : IdentityServiceError(null)
object NoIdentityServerConfigured : IdentityServiceError(null)
object TermsNotSignedException : IdentityServiceError(null)
object BulkLookupSha256NotSupported : IdentityServiceError(null)

View file

@ -29,7 +29,9 @@ internal object NetworkConstants {
// Identity server
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"

View file

@ -61,6 +61,7 @@ internal class DefaultIdentityService @Inject constructor(
private val getOpenIdTokenTask: GetOpenIdTokenTask,
private val bulkLookupTask: BulkLookupTask,
private val identityRegisterTask: IdentityRegisterTask,
private val identityPingTask: IdentityPingTask,
private val identityDisconnectTask: IdentityDisconnectTask,
private val identityRequestTokenForBindingTask: IdentityRequestTokenForBindingTask,
@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 {
val urlCandidate = url?.let { param ->
buildString {

View file

@ -32,14 +32,21 @@ internal interface IdentityAuthAPI {
/**
* 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
* https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2
*
* @return 200 in case of success
*/
@GET(NetworkConstants.URI_API_PREFIX_IDENTITY)
fun ping(): Call<Void>
@GET(NetworkConstants.URI_IDENTITY_PREFIX_PATH)
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.

View file

@ -92,6 +92,9 @@ internal abstract class IdentityModule {
@Binds
abstract fun bindIdentityServiceStore(store: RealmIdentityServiceStore): IdentityServiceStore
@Binds
abstract fun bindIdentityPingTask(task: DefaultIdentityPingTask): IdentityPingTask
@Binds
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 ->
settingsProgressItem {
id("progress${pidInfo.threePid.value}")
}
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
sealed class SetIdentityServerAction : VectorViewModelAction {
data class UpdateServerName(val url: String) : SetIdentityServerAction()
object DoChangeServerName : SetIdentityServerAction()
data class UpdateIdentityServerUrl(val url: String) : SetIdentityServerAction()
object DoChangeIdentityServerUrl : SetIdentityServerAction()
}

View file

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

View file

@ -20,8 +20,7 @@ import androidx.annotation.StringRes
import com.airbnb.mvrx.MvRxState
data class SetIdentityServerState(
val existingIdentityServer: String? = null,
val newIdentityServer: String? = null,
val newIdentityServerUrl: String? = null,
@StringRes val errorMessageId: Int? = null,
val isVerifyingServer: Boolean = false
) : 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.failure.Failure
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.TermsService
import im.vector.riotx.R
@ -62,22 +63,22 @@ class SetIdentityServerViewModel @AssistedInject constructor(
override fun handle(action: SetIdentityServerAction) {
when (action) {
is SetIdentityServerAction.UpdateServerName -> updateServerName(action)
SetIdentityServerAction.DoChangeServerName -> doChangeServerName()
is SetIdentityServerAction.UpdateIdentityServerUrl -> updateIdentityServerUrl(action)
SetIdentityServerAction.DoChangeIdentityServerUrl -> doChangeIdentityServerUrl()
}.exhaustive
}
private fun updateServerName(action: SetIdentityServerAction.UpdateServerName) {
private fun updateIdentityServerUrl(action: SetIdentityServerAction.UpdateIdentityServerUrl) {
setState {
copy(
newIdentityServer = action.url,
newIdentityServerUrl = action.url,
errorMessageId = null
)
}
}
private fun doChangeServerName() = withState {
var baseUrl: String? = it.newIdentityServer
private fun doChangeIdentityServerUrl() = withState {
var baseUrl: String? = it.newIdentityServerUrl
if (baseUrl.isNullOrBlank()) {
setState {
copy(errorMessageId = R.string.settings_discovery_please_enter_server)
@ -89,6 +90,29 @@ class SetIdentityServerViewModel @AssistedInject constructor(
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,
baseUrl,
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_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_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>