Extract code to retrieve .well-known files to its own modules.
This commit is contained in:
@@ -36,7 +36,6 @@ dependencies {
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.network)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.testtags)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
@@ -44,10 +43,9 @@ dependencies {
|
||||
implementation(projects.libraries.qrcode)
|
||||
implementation(projects.libraries.oidc.api)
|
||||
implementation(projects.libraries.uiUtils)
|
||||
implementation(projects.libraries.wellknown.api)
|
||||
implementation(libs.androidx.browser)
|
||||
implementation(platform(libs.network.retrofit.bom))
|
||||
implementation(libs.androidx.webkit)
|
||||
implementation(libs.network.retrofit)
|
||||
implementation(libs.serialization.json)
|
||||
api(projects.features.login.api)
|
||||
|
||||
@@ -65,6 +63,7 @@ dependencies {
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.oidc.test)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.wellknown.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -10,10 +10,11 @@ package io.element.android.features.login.impl.accesscontrol
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.login.impl.changeserver.AccountProviderAccessException
|
||||
import io.element.android.features.login.impl.resolver.network.ElementWellKnown
|
||||
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER
|
||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
|
||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_URL
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
@@ -152,8 +153,8 @@ class DefaultAccountProviderAccessControlTest {
|
||||
isAllowedToConnectToHomeserverResult = { isAllowedToConnectToHomeserver },
|
||||
defaultHomeserverListResult = { allowedAccountProviders },
|
||||
),
|
||||
elementWellknownRetriever = FakeElementWellknownRetriever(
|
||||
retrieveResult = { elementWellKnown }
|
||||
wellknownRetriever = FakeWellknownRetriever(
|
||||
getElementWellKnownResult = { elementWellKnown },
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,19 +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 io.element.android.features.login.impl.resolver.network.ElementWellKnown
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
|
||||
class FakeElementWellknownRetriever(
|
||||
private val retrieveResult: (String) -> ElementWellKnown? = { null },
|
||||
) : ElementWellknownRetriever {
|
||||
override suspend fun retrieve(accountProviderUrl: String): ElementWellKnown? = simulateLongTask {
|
||||
retrieveResult(accountProviderUrl)
|
||||
}
|
||||
}
|
||||
@@ -11,17 +11,17 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
||||
import io.element.android.features.login.impl.accesscontrol.ElementWellknownRetriever
|
||||
import io.element.android.features.login.impl.accesscontrol.FakeElementWellknownRetriever
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.features.login.impl.error.ChangeServerError
|
||||
import io.element.android.features.login.impl.resolver.network.ElementWellKnown
|
||||
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.core.uri.ensureProtocol
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetriever
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
@@ -113,14 +113,14 @@ class ChangeServerPresenterTest {
|
||||
|
||||
@Test
|
||||
fun `present - change server element pro required error`() = runTest {
|
||||
val retrieveResult = lambdaRecorder<String, ElementWellKnown> {
|
||||
val getElementWellKnownResult = lambdaRecorder<String, ElementWellKnown> {
|
||||
ElementWellKnown(
|
||||
enforceElementPro = true,
|
||||
)
|
||||
}
|
||||
createPresenter(
|
||||
elementWellknownRetriever = FakeElementWellknownRetriever(
|
||||
retrieveResult = retrieveResult,
|
||||
wellknownRetriever = FakeWellknownRetriever(
|
||||
getElementWellKnownResult = getElementWellKnownResult,
|
||||
),
|
||||
).test {
|
||||
val initialState = awaitItem()
|
||||
@@ -136,7 +136,7 @@ class ChangeServerPresenterTest {
|
||||
assertThat(
|
||||
(failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).applicationId
|
||||
).isEqualTo("io.element.enterprise")
|
||||
retrieveResult.assertions()
|
||||
getElementWellKnownResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_HOMESERVER_URL.ensureProtocol()))
|
||||
}
|
||||
@@ -146,13 +146,13 @@ class ChangeServerPresenterTest {
|
||||
authenticationService: FakeMatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
||||
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
elementWellknownRetriever: ElementWellknownRetriever = FakeElementWellknownRetriever(),
|
||||
wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(),
|
||||
) = ChangeServerPresenter(
|
||||
authenticationService = authenticationService,
|
||||
accountProviderDataSource = accountProviderDataSource,
|
||||
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
|
||||
enterpriseService = enterpriseService,
|
||||
elementWellknownRetriever = elementWellknownRetriever,
|
||||
wellknownRetriever = wellknownRetriever,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +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
|
||||
|
||||
class FakeWellknownRequest : WellknownRequest {
|
||||
private var resultMap: Map<String, WellKnown> = emptyMap()
|
||||
fun givenResultMap(map: Map<String, WellKnown>) {
|
||||
resultMap = map
|
||||
}
|
||||
|
||||
override suspend fun execute(baseUrl: String): WellKnown {
|
||||
return resultMap[baseUrl] ?: error("No result provided for $baseUrl")
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,10 @@ import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.login.impl.DefaultLoginUserStory
|
||||
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
||||
import io.element.android.features.login.impl.accesscontrol.ElementWellknownRetriever
|
||||
import io.element.android.features.login.impl.accesscontrol.FakeElementWellknownRetriever
|
||||
import io.element.android.features.login.impl.login.LoginHelper
|
||||
import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever
|
||||
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
||||
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
@@ -34,6 +33,7 @@ import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationSer
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.oidc.api.OidcActionFlow
|
||||
import io.element.android.libraries.oidc.test.customtab.FakeOidcActionFlow
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetriever
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -238,7 +238,7 @@ private fun createPresenter(
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
elementWellknownRetriever: ElementWellknownRetriever = FakeElementWellknownRetriever(),
|
||||
wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(),
|
||||
rageshakeFeatureAvailability: () -> Boolean = { true },
|
||||
loginHelper: LoginHelper = createLoginHelper(),
|
||||
) = OnBoardingPresenter(
|
||||
@@ -248,7 +248,7 @@ private fun createPresenter(
|
||||
enterpriseService = enterpriseService,
|
||||
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
|
||||
enterpriseService = enterpriseService,
|
||||
elementWellknownRetriever = elementWellknownRetriever,
|
||||
wellknownRetriever = wellknownRetriever,
|
||||
),
|
||||
rageshakeFeatureAvailability = rageshakeFeatureAvailability,
|
||||
loginHelper = loginHelper,
|
||||
|
||||
@@ -14,15 +14,15 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
||||
import io.element.android.features.login.impl.accesscontrol.ElementWellknownRetriever
|
||||
import io.element.android.features.login.impl.accesscontrol.FakeElementWellknownRetriever
|
||||
import io.element.android.features.login.impl.changeserver.AccountProviderAccessException
|
||||
import io.element.android.features.login.impl.qrcode.FakeQrCodeLoginManager
|
||||
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrLoginException
|
||||
import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginData
|
||||
import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginDataFactory
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetriever
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
@@ -162,14 +162,14 @@ class QrCodeScanPresenterTest {
|
||||
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
qrCodeLoginManager: FakeQrCodeLoginManager = FakeQrCodeLoginManager(),
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
elementWellknownRetriever: ElementWellknownRetriever = FakeElementWellknownRetriever(),
|
||||
wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(),
|
||||
) = QrCodeScanPresenter(
|
||||
qrCodeLoginDataFactory = qrCodeLoginDataFactory,
|
||||
qrCodeLoginManager = qrCodeLoginManager,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
|
||||
enterpriseService = enterpriseService,
|
||||
elementWellknownRetriever = elementWellknownRetriever,
|
||||
wellknownRetriever = wellknownRetriever,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,12 +13,14 @@ import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.login.impl.changeserver.aChangeServerState
|
||||
import io.element.android.features.login.impl.resolver.HomeserverResolver
|
||||
import io.element.android.features.login.impl.resolver.network.FakeWellknownRequest
|
||||
import io.element.android.features.login.impl.resolver.network.WellKnown
|
||||
import io.element.android.features.login.impl.resolver.network.WellKnownBaseConfig
|
||||
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
||||
import io.element.android.libraries.wellknown.api.WellKnown
|
||||
import io.element.android.libraries.wellknown.api.WellKnownBaseConfig
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
@@ -30,9 +32,9 @@ class SearchAccountProviderPresenterTest {
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val fakeWellknownRequest = FakeWellknownRequest()
|
||||
val fakeWellknownRetriever = FakeWellknownRetriever()
|
||||
val presenter = SearchAccountProviderPresenter(
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever),
|
||||
changeServerPresenter = { aChangeServerState() }
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
@@ -46,9 +48,9 @@ class SearchAccountProviderPresenterTest {
|
||||
|
||||
@Test
|
||||
fun `present - enter text no result`() = runTest {
|
||||
val fakeWellknownRequest = FakeWellknownRequest()
|
||||
val fakeWellknownRetriever = FakeWellknownRetriever()
|
||||
val presenter = SearchAccountProviderPresenter(
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever),
|
||||
changeServerPresenter = { aChangeServerState() }
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
@@ -66,9 +68,9 @@ class SearchAccountProviderPresenterTest {
|
||||
|
||||
@Test
|
||||
fun `present - enter valid url no wellknown`() = runTest {
|
||||
val fakeWellknownRequest = FakeWellknownRequest()
|
||||
val fakeWellknownRetriever = FakeWellknownRetriever()
|
||||
val presenter = SearchAccountProviderPresenter(
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever),
|
||||
changeServerPresenter = { aChangeServerState() }
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
@@ -92,14 +94,20 @@ class SearchAccountProviderPresenterTest {
|
||||
|
||||
@Test
|
||||
fun `present - enter text one result with wellknown`() = runTest {
|
||||
val fakeWellknownRequest = FakeWellknownRequest()
|
||||
fakeWellknownRequest.givenResultMap(
|
||||
mapOf(
|
||||
"https://test.io" to aWellKnown(),
|
||||
)
|
||||
val getWellKnownResult = lambdaRecorder<String, WellKnown> {
|
||||
when (it) {
|
||||
"https://test.org" -> error("not found")
|
||||
"https://test.com" -> error("not found")
|
||||
"https://test.io" -> aWellKnown()
|
||||
"https://test" -> error("not found")
|
||||
else -> error("should not happen")
|
||||
}
|
||||
}
|
||||
val fakeWellknownRetriever = FakeWellknownRetriever(
|
||||
getWellKnownResult = getWellKnownResult,
|
||||
)
|
||||
val presenter = SearchAccountProviderPresenter(
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever),
|
||||
changeServerPresenter = { aChangeServerState() }
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
@@ -118,6 +126,65 @@ class SearchAccountProviderPresenterTest {
|
||||
)
|
||||
)
|
||||
)
|
||||
getWellKnownResult.assertions().isCalledExactly(4)
|
||||
.withSequence(
|
||||
listOf(value("https://test.org")),
|
||||
listOf(value("https://test.com")),
|
||||
listOf(value("https://test.io")),
|
||||
listOf(value("https://test")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - enter text two results with wellknown`() = runTest {
|
||||
val getWellKnownResult = lambdaRecorder<String, WellKnown> {
|
||||
when (it) {
|
||||
"https://test.org" -> aWellKnown()
|
||||
"https://test.com" -> error("not found")
|
||||
"https://test.io" -> aWellKnown()
|
||||
"https://test" -> error("not found")
|
||||
else -> error("should not happen")
|
||||
}
|
||||
}
|
||||
val fakeWellknownRetriever = FakeWellknownRetriever(
|
||||
getWellKnownResult = getWellKnownResult,
|
||||
)
|
||||
val presenter = SearchAccountProviderPresenter(
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever),
|
||||
changeServerPresenter = { aChangeServerState() }
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("test"))
|
||||
val withInputState = awaitItem()
|
||||
assertThat(withInputState.userInput).isEqualTo("test")
|
||||
assertThat(initialState.userInputResult).isEqualTo(AsyncData.Uninitialized)
|
||||
assertThat(awaitItem().userInputResult).isInstanceOf(AsyncData.Loading::class.java)
|
||||
assertThat(awaitItem().userInputResult).isEqualTo(
|
||||
AsyncData.Success(
|
||||
listOf(
|
||||
aHomeserverData(homeserverUrl = "https://test.org"),
|
||||
)
|
||||
)
|
||||
)
|
||||
assertThat(awaitItem().userInputResult).isEqualTo(
|
||||
AsyncData.Success(
|
||||
listOf(
|
||||
aHomeserverData(homeserverUrl = "https://test.org"),
|
||||
aHomeserverData(homeserverUrl = "https://test.io"),
|
||||
)
|
||||
)
|
||||
)
|
||||
getWellKnownResult.assertions().isCalledExactly(4)
|
||||
.withSequence(
|
||||
listOf(value("https://test.org")),
|
||||
listOf(value("https://test.com")),
|
||||
listOf(value("https://test.io")),
|
||||
listOf(value("https://test")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
13
libraries/wellknown/api/build.gradle.kts
Normal file
13
libraries/wellknown/api/build.gradle.kts
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.wellknown.api"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* 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.libraries.wellknown.api
|
||||
|
||||
data class ElementWellKnown(
|
||||
val registrationHelperUrl: String? = null,
|
||||
val enforceElementPro: Boolean? = null,
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.libraries.wellknown.api
|
||||
|
||||
data class WellKnown(
|
||||
val homeServer: WellKnownBaseConfig? = null,
|
||||
val identityServer: WellKnownBaseConfig? = null,
|
||||
)
|
||||
|
||||
data class WellKnownBaseConfig(
|
||||
val baseURL: String? = null
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* 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.libraries.wellknown.api
|
||||
|
||||
interface WellknownRetriever {
|
||||
suspend fun getWellKnown(baseUrl: String): WellKnown?
|
||||
suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown?
|
||||
}
|
||||
32
libraries/wellknown/impl/build.gradle.kts
Normal file
32
libraries/wellknown/impl/build.gradle.kts
Normal file
@@ -0,0 +1,32 @@
|
||||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
id("kotlin-parcelize")
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.wellknown.impl"
|
||||
}
|
||||
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.annotationjvm)
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(platform(libs.network.retrofit.bom))
|
||||
implementation(libs.network.retrofit)
|
||||
implementation(libs.serialization.json)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.network)
|
||||
implementation(projects.libraries.wellknown.api)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.libraries.wellknown.impl
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.core.uri.ensureProtocol
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.network.RetrofitFactory
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||
import io.element.android.libraries.wellknown.api.WellKnown
|
||||
import io.element.android.libraries.wellknown.api.WellKnownBaseConfig
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetriever
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultWellknownRetriever @Inject constructor(
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
) : WellknownRetriever {
|
||||
override suspend fun getWellKnown(baseUrl: String): WellKnown? {
|
||||
val wellknownApi = buildWellknownApi(baseUrl) ?: return null
|
||||
return try {
|
||||
wellknownApi.getWellKnown().map()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to retrieve well-known data for $baseUrl")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? {
|
||||
val wellknownApi = buildWellknownApi(baseUrl) ?: return null
|
||||
return try {
|
||||
wellknownApi.getElementWellKnown().map()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to retrieve Element well-known data for $baseUrl")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildWellknownApi(accountProviderUrl: String): WellknownAPI? {
|
||||
return try {
|
||||
retrofitFactory.create(accountProviderUrl.ensureProtocol())
|
||||
.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")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun InternalElementWellKnown.map() = ElementWellKnown(
|
||||
registrationHelperUrl = registrationHelperUrl,
|
||||
enforceElementPro = enforceElementPro,
|
||||
)
|
||||
|
||||
private fun InternalWellKnown.map() = WellKnown(
|
||||
homeServer = homeServer?.map(),
|
||||
identityServer = identityServer?.map(),
|
||||
)
|
||||
|
||||
private fun InternalWellKnownBaseConfig.map() = WellKnownBaseConfig(
|
||||
baseURL = baseURL,
|
||||
)
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
* 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.resolver.network
|
||||
package io.element.android.libraries.wellknown.impl
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -20,7 +20,7 @@ import kotlinx.serialization.Serializable
|
||||
* .
|
||||
*/
|
||||
@Serializable
|
||||
data class ElementWellKnown(
|
||||
data class InternalElementWellKnown(
|
||||
@SerialName("registration_helper_url")
|
||||
val registrationHelperUrl: String? = null,
|
||||
@SerialName("enforce_element_pro")
|
||||
@@ -5,9 +5,8 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.resolver.network
|
||||
package io.element.android.libraries.wellknown.impl
|
||||
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@@ -26,13 +25,9 @@ import kotlinx.serialization.Serializable
|
||||
* .
|
||||
*/
|
||||
@Serializable
|
||||
data class WellKnown(
|
||||
data class InternalWellKnown(
|
||||
@SerialName("m.homeserver")
|
||||
val homeServer: WellKnownBaseConfig? = null,
|
||||
val homeServer: InternalWellKnownBaseConfig? = null,
|
||||
@SerialName("m.identity_server")
|
||||
val identityServer: WellKnownBaseConfig? = null,
|
||||
) {
|
||||
fun isValid(): Boolean {
|
||||
return homeServer?.baseURL?.isNotBlank().orFalse()
|
||||
}
|
||||
}
|
||||
val identityServer: InternalWellKnownBaseConfig? = null,
|
||||
)
|
||||
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.resolver.network
|
||||
package io.element.android.libraries.wellknown.impl
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -20,7 +20,7 @@ import kotlinx.serialization.Serializable
|
||||
* .
|
||||
*/
|
||||
@Serializable
|
||||
data class WellKnownBaseConfig(
|
||||
data class InternalWellKnownBaseConfig(
|
||||
@SerialName("base_url")
|
||||
val baseURL: String? = null
|
||||
)
|
||||
@@ -5,14 +5,14 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.resolver.network
|
||||
package io.element.android.libraries.wellknown.impl
|
||||
|
||||
import retrofit2.http.GET
|
||||
|
||||
internal interface WellknownAPI {
|
||||
@GET(".well-known/matrix/client")
|
||||
suspend fun getWellKnown(): WellKnown
|
||||
suspend fun getWellKnown(): InternalWellKnown
|
||||
|
||||
@GET(".well-known/element/element.json")
|
||||
suspend fun getElementWellKnown(): ElementWellKnown
|
||||
suspend fun getElementWellKnown(): InternalElementWellKnown
|
||||
}
|
||||
19
libraries/wellknown/test/build.gradle.kts
Normal file
19
libraries/wellknown/test/build.gradle.kts
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.wellknown.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.wellknown.api)
|
||||
implementation(projects.tests.testutils)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.wellknown.test
|
||||
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||
import io.element.android.libraries.wellknown.api.WellKnown
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetriever
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
|
||||
class FakeWellknownRetriever(
|
||||
private val getWellKnownResult: (String) -> WellKnown? = { null },
|
||||
private val getElementWellKnownResult: (String) -> ElementWellKnown? = { null },
|
||||
) : WellknownRetriever {
|
||||
override suspend fun getWellKnown(baseUrl: String): WellKnown? = simulateLongTask {
|
||||
getWellKnownResult(baseUrl)
|
||||
}
|
||||
|
||||
override suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? = simulateLongTask {
|
||||
getElementWellKnownResult(baseUrl)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user