Extract code to retrieve .well-known files to its own modules.

This commit is contained in:
Benoit Marty 2025-08-08 12:21:36 +02:00
parent 1afcce2b97
commit db64ce3142
26 changed files with 337 additions and 199 deletions

View file

@ -13,12 +13,13 @@ import io.element.android.features.login.api.accesscontrol.AccountProviderAccess
import io.element.android.features.login.impl.changeserver.AccountProviderAccessException
import io.element.android.libraries.core.uri.ensureProtocol
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.wellknown.api.WellknownRetriever
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultAccountProviderAccessControl @Inject constructor(
private val enterpriseService: EnterpriseService,
private val elementWellknownRetriever: ElementWellknownRetriever,
private val wellknownRetriever: WellknownRetriever,
) : AccountProviderAccessControl {
override suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String) = try {
assertIsAllowedToConnectToAccountProvider(
@ -37,8 +38,8 @@ class DefaultAccountProviderAccessControl @Inject constructor(
) {
if (enterpriseService.isEnterpriseBuild.not()) {
// Ensure that Element Pro is not required for this account provider
val wellKnown = elementWellknownRetriever.retrieve(
accountProviderUrl = accountProviderUrl.ensureProtocol(),
val wellKnown = wellknownRetriever.getElementWellKnown(
baseUrl = accountProviderUrl.ensureProtocol(),
)
if (wellKnown?.enforceElementPro == true) {
throw AccountProviderAccessException.NeedElementProException(

View file

@ -1,42 +0,0 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.login.impl.accesscontrol
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.login.impl.resolver.network.ElementWellKnown
import io.element.android.features.login.impl.resolver.network.WellknownAPI
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.network.RetrofitFactory
import timber.log.Timber
import javax.inject.Inject
interface ElementWellknownRetriever {
suspend fun retrieve(accountProviderUrl: String): ElementWellKnown?
}
@ContributesBinding(AppScope::class)
class DefaultElementWellknownRetriever @Inject constructor(
private val retrofitFactory: RetrofitFactory,
) : ElementWellknownRetriever {
override suspend fun retrieve(accountProviderUrl: String): ElementWellKnown? {
val wellknownApi = try {
retrofitFactory.create(accountProviderUrl)
.create(WellknownAPI::class.java)
} catch (e: Exception) {
// If the base URL is not valid, we cannot retrieve the well-known data
Timber.e(e, "Failed to create Retrofit instance for $accountProviderUrl")
return null
}
return try {
wellknownApi.getElementWellKnown()
} catch (e: Exception) {
Timber.e(e, "Failed to retrieve Element well-known data for $accountProviderUrl")
null
}
}
}

View file

@ -7,13 +7,14 @@
package io.element.android.features.login.impl.resolver
import io.element.android.features.login.impl.resolver.network.WellknownRequest
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.parallelMap
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.core.uri.ensureProtocol
import io.element.android.libraries.core.uri.isValidUrl
import io.element.android.libraries.wellknown.api.WellKnown
import io.element.android.libraries.wellknown.api.WellknownRetriever
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
@ -27,7 +28,7 @@ import javax.inject.Inject
*/
class HomeserverResolver @Inject constructor(
private val dispatchers: CoroutineDispatchers,
private val wellknownRequest: WellknownRequest,
private val wellknownRetriever: WellknownRetriever,
) {
fun resolve(userInput: String): Flow<List<HomeserverData>> = flow {
val flowContext = currentCoroutineContext()
@ -41,7 +42,7 @@ class HomeserverResolver @Inject constructor(
list.parallelMap { url ->
val wellKnown = tryOrNull {
withTimeout(5000) {
wellknownRequest.execute(url)
wellknownRetriever.getWellKnown(url)
}
}
val isValid = wellKnown?.isValid().orFalse()
@ -86,3 +87,7 @@ class HomeserverResolver @Inject constructor(
}
}
}
private fun WellKnown.isValid(): Boolean {
return homeServer?.baseURL?.isNotBlank().orFalse()
}

View file

@ -1,27 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.login.impl.resolver.network
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.network.RetrofitFactory
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultWellknownRequest @Inject constructor(
private val retrofitFactory: RetrofitFactory,
) : WellknownRequest {
/**
* Return the WellKnown data, if found.
* @param baseUrl for instance https://matrix.org
*/
override suspend fun execute(baseUrl: String): WellKnown {
val wellknownApi = retrofitFactory.create(baseUrl)
.create(WellknownAPI::class.java)
return wellknownApi.getWellKnown()
}
}

View file

@ -1,28 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.login.impl.resolver.network
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Example:
* <pre>
* {
* "registration_helper_url": "https://element.io"
* }
* </pre>
* .
*/
@Serializable
data class ElementWellKnown(
@SerialName("registration_helper_url")
val registrationHelperUrl: String? = null,
@SerialName("enforce_element_pro")
val enforceElementPro: Boolean? = null,
)

View file

@ -1,38 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.login.impl.resolver.network
import io.element.android.libraries.core.bool.orFalse
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
* <pre>
* {
* "m.homeserver": {
* "base_url": "https://matrix.org"
* },
* "m.identity_server": {
* "base_url": "https://vector.im"
* },
* }
* </pre>
* .
*/
@Serializable
data class WellKnown(
@SerialName("m.homeserver")
val homeServer: WellKnownBaseConfig? = null,
@SerialName("m.identity_server")
val identityServer: WellKnownBaseConfig? = null,
) {
fun isValid(): Boolean {
return homeServer?.baseURL?.isNotBlank().orFalse()
}
}

View file

@ -1,26 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.login.impl.resolver.network
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
* <pre>
* {
* "base_url": "https://element.io"
* }
* </pre>
* .
*/
@Serializable
data class WellKnownBaseConfig(
@SerialName("base_url")
val baseURL: String? = null
)

View file

@ -1,18 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.login.impl.resolver.network
import retrofit2.http.GET
internal interface WellknownAPI {
@GET(".well-known/matrix/client")
suspend fun getWellKnown(): WellKnown
@GET(".well-known/element/element.json")
suspend fun getElementWellKnown(): ElementWellKnown
}

View file

@ -1,15 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.login.impl.resolver.network
interface WellknownRequest {
/**
* Return the WellKnown data, or throw an error if not found.
* @param baseUrl for instance https://matrix.org
*/
suspend fun execute(baseUrl: String): WellKnown
}

View file

@ -10,12 +10,10 @@ package io.element.android.features.login.impl.web
import androidx.core.net.toUri
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.appconfig.AuthenticationConfig
import io.element.android.features.login.impl.resolver.network.WellknownAPI
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.network.RetrofitFactory
import io.element.android.libraries.wellknown.api.WellknownRetriever
import timber.log.Timber
import java.net.HttpURLConnection
import javax.inject.Inject
interface WebClientUrlForAuthenticationRetriever {
@ -24,24 +22,16 @@ interface WebClientUrlForAuthenticationRetriever {
@ContributesBinding(AppScope::class)
class DefaultWebClientUrlForAuthenticationRetriever @Inject constructor(
private val retrofitFactory: RetrofitFactory,
private val wellknownRetriever: WellknownRetriever,
) : WebClientUrlForAuthenticationRetriever {
override suspend fun retrieve(homeServerUrl: String): String {
if (homeServerUrl != AuthenticationConfig.MATRIX_ORG_URL) {
Timber.w("Temporary account creation flow is only supported on matrix.org")
throw AccountCreationNotSupported()
}
val wellknownApi = retrofitFactory.create(homeServerUrl)
.create(WellknownAPI::class.java)
val result = try {
wellknownApi.getElementWellKnown()
} catch (e: retrofit2.HttpException) {
throw when {
e.code() == HttpURLConnection.HTTP_NOT_FOUND -> AccountCreationNotSupported()
else -> e
}
}
val registrationHelperUrl = result.registrationHelperUrl
val wellknown = wellknownRetriever.getElementWellKnown(homeServerUrl)
?: throw AccountCreationNotSupported()
val registrationHelperUrl = wellknown.registrationHelperUrl
return if (registrationHelperUrl != null) {
registrationHelperUrl.toUri()
.buildUpon()