Extract code to retrieve .well-known files to its own modules.
This commit is contained in:
parent
b1a48ec863
commit
dbe5bb767b
26 changed files with 337 additions and 199 deletions
|
|
@ -36,7 +36,6 @@ dependencies {
|
||||||
implementation(projects.libraries.featureflag.api)
|
implementation(projects.libraries.featureflag.api)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.network)
|
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(projects.libraries.testtags)
|
implementation(projects.libraries.testtags)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
|
|
@ -44,10 +43,9 @@ dependencies {
|
||||||
implementation(projects.libraries.qrcode)
|
implementation(projects.libraries.qrcode)
|
||||||
implementation(projects.libraries.oidc.api)
|
implementation(projects.libraries.oidc.api)
|
||||||
implementation(projects.libraries.uiUtils)
|
implementation(projects.libraries.uiUtils)
|
||||||
|
implementation(projects.libraries.wellknown.api)
|
||||||
implementation(libs.androidx.browser)
|
implementation(libs.androidx.browser)
|
||||||
implementation(platform(libs.network.retrofit.bom))
|
|
||||||
implementation(libs.androidx.webkit)
|
implementation(libs.androidx.webkit)
|
||||||
implementation(libs.network.retrofit)
|
|
||||||
implementation(libs.serialization.json)
|
implementation(libs.serialization.json)
|
||||||
api(projects.features.login.api)
|
api(projects.features.login.api)
|
||||||
|
|
||||||
|
|
@ -65,6 +63,7 @@ dependencies {
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.oidc.test)
|
testImplementation(projects.libraries.oidc.test)
|
||||||
testImplementation(projects.libraries.permissions.test)
|
testImplementation(projects.libraries.permissions.test)
|
||||||
|
testImplementation(projects.libraries.wellknown.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
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.features.login.impl.changeserver.AccountProviderAccessException
|
||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
import io.element.android.libraries.core.uri.ensureProtocol
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.wellknown.api.WellknownRetriever
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultAccountProviderAccessControl @Inject constructor(
|
class DefaultAccountProviderAccessControl @Inject constructor(
|
||||||
private val enterpriseService: EnterpriseService,
|
private val enterpriseService: EnterpriseService,
|
||||||
private val elementWellknownRetriever: ElementWellknownRetriever,
|
private val wellknownRetriever: WellknownRetriever,
|
||||||
) : AccountProviderAccessControl {
|
) : AccountProviderAccessControl {
|
||||||
override suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String) = try {
|
override suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String) = try {
|
||||||
assertIsAllowedToConnectToAccountProvider(
|
assertIsAllowedToConnectToAccountProvider(
|
||||||
|
|
@ -37,8 +38,8 @@ class DefaultAccountProviderAccessControl @Inject constructor(
|
||||||
) {
|
) {
|
||||||
if (enterpriseService.isEnterpriseBuild.not()) {
|
if (enterpriseService.isEnterpriseBuild.not()) {
|
||||||
// Ensure that Element Pro is not required for this account provider
|
// Ensure that Element Pro is not required for this account provider
|
||||||
val wellKnown = elementWellknownRetriever.retrieve(
|
val wellKnown = wellknownRetriever.getElementWellKnown(
|
||||||
accountProviderUrl = accountProviderUrl.ensureProtocol(),
|
baseUrl = accountProviderUrl.ensureProtocol(),
|
||||||
)
|
)
|
||||||
if (wellKnown?.enforceElementPro == true) {
|
if (wellKnown?.enforceElementPro == true) {
|
||||||
throw AccountProviderAccessException.NeedElementProException(
|
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
|
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.bool.orFalse
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.core.coroutine.parallelMap
|
import io.element.android.libraries.core.coroutine.parallelMap
|
||||||
import io.element.android.libraries.core.data.tryOrNull
|
import io.element.android.libraries.core.data.tryOrNull
|
||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
import io.element.android.libraries.core.uri.ensureProtocol
|
||||||
import io.element.android.libraries.core.uri.isValidUrl
|
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.currentCoroutineContext
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
@ -27,7 +28,7 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class HomeserverResolver @Inject constructor(
|
class HomeserverResolver @Inject constructor(
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
private val wellknownRequest: WellknownRequest,
|
private val wellknownRetriever: WellknownRetriever,
|
||||||
) {
|
) {
|
||||||
fun resolve(userInput: String): Flow<List<HomeserverData>> = flow {
|
fun resolve(userInput: String): Flow<List<HomeserverData>> = flow {
|
||||||
val flowContext = currentCoroutineContext()
|
val flowContext = currentCoroutineContext()
|
||||||
|
|
@ -41,7 +42,7 @@ class HomeserverResolver @Inject constructor(
|
||||||
list.parallelMap { url ->
|
list.parallelMap { url ->
|
||||||
val wellKnown = tryOrNull {
|
val wellKnown = tryOrNull {
|
||||||
withTimeout(5000) {
|
withTimeout(5000) {
|
||||||
wellknownRequest.execute(url)
|
wellknownRetriever.getWellKnown(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val isValid = wellKnown?.isValid().orFalse()
|
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 androidx.core.net.toUri
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
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.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
||||||
import io.element.android.libraries.di.AppScope
|
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 timber.log.Timber
|
||||||
import java.net.HttpURLConnection
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
interface WebClientUrlForAuthenticationRetriever {
|
interface WebClientUrlForAuthenticationRetriever {
|
||||||
|
|
@ -24,24 +22,16 @@ interface WebClientUrlForAuthenticationRetriever {
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultWebClientUrlForAuthenticationRetriever @Inject constructor(
|
class DefaultWebClientUrlForAuthenticationRetriever @Inject constructor(
|
||||||
private val retrofitFactory: RetrofitFactory,
|
private val wellknownRetriever: WellknownRetriever,
|
||||||
) : WebClientUrlForAuthenticationRetriever {
|
) : WebClientUrlForAuthenticationRetriever {
|
||||||
override suspend fun retrieve(homeServerUrl: String): String {
|
override suspend fun retrieve(homeServerUrl: String): String {
|
||||||
if (homeServerUrl != AuthenticationConfig.MATRIX_ORG_URL) {
|
if (homeServerUrl != AuthenticationConfig.MATRIX_ORG_URL) {
|
||||||
Timber.w("Temporary account creation flow is only supported on matrix.org")
|
Timber.w("Temporary account creation flow is only supported on matrix.org")
|
||||||
throw AccountCreationNotSupported()
|
throw AccountCreationNotSupported()
|
||||||
}
|
}
|
||||||
val wellknownApi = retrofitFactory.create(homeServerUrl)
|
val wellknown = wellknownRetriever.getElementWellKnown(homeServerUrl)
|
||||||
.create(WellknownAPI::class.java)
|
?: throw AccountCreationNotSupported()
|
||||||
val result = try {
|
val registrationHelperUrl = wellknown.registrationHelperUrl
|
||||||
wellknownApi.getElementWellKnown()
|
|
||||||
} catch (e: retrofit2.HttpException) {
|
|
||||||
throw when {
|
|
||||||
e.code() == HttpURLConnection.HTTP_NOT_FOUND -> AccountCreationNotSupported()
|
|
||||||
else -> e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val registrationHelperUrl = result.registrationHelperUrl
|
|
||||||
return if (registrationHelperUrl != null) {
|
return if (registrationHelperUrl != null) {
|
||||||
registrationHelperUrl.toUri()
|
registrationHelperUrl.toUri()
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,11 @@ package io.element.android.features.login.impl.accesscontrol
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.login.impl.changeserver.AccountProviderAccessException
|
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
|
||||||
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
|
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.matrix.test.AN_ACCOUNT_PROVIDER_URL
|
||||||
|
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Assert.assertThrows
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -152,8 +153,8 @@ class DefaultAccountProviderAccessControlTest {
|
||||||
isAllowedToConnectToHomeserverResult = { isAllowedToConnectToHomeserver },
|
isAllowedToConnectToHomeserverResult = { isAllowedToConnectToHomeserver },
|
||||||
defaultHomeserverListResult = { allowedAccountProviders },
|
defaultHomeserverListResult = { allowedAccountProviders },
|
||||||
),
|
),
|
||||||
elementWellknownRetriever = FakeElementWellknownRetriever(
|
wellknownRetriever = FakeWellknownRetriever(
|
||||||
retrieveResult = { elementWellKnown }
|
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.api.EnterpriseService
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
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.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.AccountProvider
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
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.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.architecture.AsyncData
|
||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
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
|
||||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
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.WarmUpRule
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
|
|
@ -113,14 +113,14 @@ class ChangeServerPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - change server element pro required error`() = runTest {
|
fun `present - change server element pro required error`() = runTest {
|
||||||
val retrieveResult = lambdaRecorder<String, ElementWellKnown> {
|
val getElementWellKnownResult = lambdaRecorder<String, ElementWellKnown> {
|
||||||
ElementWellKnown(
|
ElementWellKnown(
|
||||||
enforceElementPro = true,
|
enforceElementPro = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
createPresenter(
|
createPresenter(
|
||||||
elementWellknownRetriever = FakeElementWellknownRetriever(
|
wellknownRetriever = FakeWellknownRetriever(
|
||||||
retrieveResult = retrieveResult,
|
getElementWellKnownResult = getElementWellKnownResult,
|
||||||
),
|
),
|
||||||
).test {
|
).test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
|
|
@ -136,7 +136,7 @@ class ChangeServerPresenterTest {
|
||||||
assertThat(
|
assertThat(
|
||||||
(failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).applicationId
|
(failureState.changeServerAction.errorOrNull() as ChangeServerError.NeedElementPro).applicationId
|
||||||
).isEqualTo("io.element.enterprise")
|
).isEqualTo("io.element.enterprise")
|
||||||
retrieveResult.assertions()
|
getElementWellKnownResult.assertions()
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(value(A_HOMESERVER_URL.ensureProtocol()))
|
.with(value(A_HOMESERVER_URL.ensureProtocol()))
|
||||||
}
|
}
|
||||||
|
|
@ -146,13 +146,13 @@ class ChangeServerPresenterTest {
|
||||||
authenticationService: FakeMatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
authenticationService: FakeMatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
||||||
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||||
elementWellknownRetriever: ElementWellknownRetriever = FakeElementWellknownRetriever(),
|
wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(),
|
||||||
) = ChangeServerPresenter(
|
) = ChangeServerPresenter(
|
||||||
authenticationService = authenticationService,
|
authenticationService = authenticationService,
|
||||||
accountProviderDataSource = accountProviderDataSource,
|
accountProviderDataSource = accountProviderDataSource,
|
||||||
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
|
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
|
||||||
enterpriseService = enterpriseService,
|
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.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.login.impl.DefaultLoginUserStory
|
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.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.login.LoginHelper
|
||||||
import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever
|
import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever
|
||||||
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
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.architecture.AsyncData
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
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.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.libraries.oidc.api.OidcActionFlow
|
import io.element.android.libraries.oidc.api.OidcActionFlow
|
||||||
import io.element.android.libraries.oidc.test.customtab.FakeOidcActionFlow
|
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.WarmUpRule
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -238,7 +238,7 @@ private fun createPresenter(
|
||||||
buildMeta: BuildMeta = aBuildMeta(),
|
buildMeta: BuildMeta = aBuildMeta(),
|
||||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||||
elementWellknownRetriever: ElementWellknownRetriever = FakeElementWellknownRetriever(),
|
wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(),
|
||||||
rageshakeFeatureAvailability: () -> Boolean = { true },
|
rageshakeFeatureAvailability: () -> Boolean = { true },
|
||||||
loginHelper: LoginHelper = createLoginHelper(),
|
loginHelper: LoginHelper = createLoginHelper(),
|
||||||
) = OnBoardingPresenter(
|
) = OnBoardingPresenter(
|
||||||
|
|
@ -248,7 +248,7 @@ private fun createPresenter(
|
||||||
enterpriseService = enterpriseService,
|
enterpriseService = enterpriseService,
|
||||||
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
|
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
|
||||||
enterpriseService = enterpriseService,
|
enterpriseService = enterpriseService,
|
||||||
elementWellknownRetriever = elementWellknownRetriever,
|
wellknownRetriever = wellknownRetriever,
|
||||||
),
|
),
|
||||||
rageshakeFeatureAvailability = rageshakeFeatureAvailability,
|
rageshakeFeatureAvailability = rageshakeFeatureAvailability,
|
||||||
loginHelper = loginHelper,
|
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.api.EnterpriseService
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
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.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.changeserver.AccountProviderAccessException
|
||||||
import io.element.android.features.login.impl.qrcode.FakeQrCodeLoginManager
|
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.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
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.api.auth.qrlogin.QrLoginException
|
||||||
import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginData
|
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.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.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
|
|
@ -162,14 +162,14 @@ class QrCodeScanPresenterTest {
|
||||||
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||||
qrCodeLoginManager: FakeQrCodeLoginManager = FakeQrCodeLoginManager(),
|
qrCodeLoginManager: FakeQrCodeLoginManager = FakeQrCodeLoginManager(),
|
||||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||||
elementWellknownRetriever: ElementWellknownRetriever = FakeElementWellknownRetriever(),
|
wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(),
|
||||||
) = QrCodeScanPresenter(
|
) = QrCodeScanPresenter(
|
||||||
qrCodeLoginDataFactory = qrCodeLoginDataFactory,
|
qrCodeLoginDataFactory = qrCodeLoginDataFactory,
|
||||||
qrCodeLoginManager = qrCodeLoginManager,
|
qrCodeLoginManager = qrCodeLoginManager,
|
||||||
coroutineDispatchers = coroutineDispatchers,
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
|
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
|
||||||
enterpriseService = enterpriseService,
|
enterpriseService = enterpriseService,
|
||||||
elementWellknownRetriever = elementWellknownRetriever,
|
wellknownRetriever = wellknownRetriever,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,14 @@ import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.login.impl.changeserver.aChangeServerState
|
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.HomeserverResolver
|
||||||
import io.element.android.features.login.impl.resolver.network.FakeWellknownRequest
|
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
||||||
import io.element.android.features.login.impl.resolver.network.WellKnown
|
|
||||||
import io.element.android.features.login.impl.resolver.network.WellKnownBaseConfig
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
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.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 io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
|
@ -30,9 +32,9 @@ class SearchAccountProviderPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state`() = runTest {
|
fun `present - initial state`() = runTest {
|
||||||
val fakeWellknownRequest = FakeWellknownRequest()
|
val fakeWellknownRetriever = FakeWellknownRetriever()
|
||||||
val presenter = SearchAccountProviderPresenter(
|
val presenter = SearchAccountProviderPresenter(
|
||||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever),
|
||||||
changeServerPresenter = { aChangeServerState() }
|
changeServerPresenter = { aChangeServerState() }
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -46,9 +48,9 @@ class SearchAccountProviderPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - enter text no result`() = runTest {
|
fun `present - enter text no result`() = runTest {
|
||||||
val fakeWellknownRequest = FakeWellknownRequest()
|
val fakeWellknownRetriever = FakeWellknownRetriever()
|
||||||
val presenter = SearchAccountProviderPresenter(
|
val presenter = SearchAccountProviderPresenter(
|
||||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever),
|
||||||
changeServerPresenter = { aChangeServerState() }
|
changeServerPresenter = { aChangeServerState() }
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -66,9 +68,9 @@ class SearchAccountProviderPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - enter valid url no wellknown`() = runTest {
|
fun `present - enter valid url no wellknown`() = runTest {
|
||||||
val fakeWellknownRequest = FakeWellknownRequest()
|
val fakeWellknownRetriever = FakeWellknownRetriever()
|
||||||
val presenter = SearchAccountProviderPresenter(
|
val presenter = SearchAccountProviderPresenter(
|
||||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever),
|
||||||
changeServerPresenter = { aChangeServerState() }
|
changeServerPresenter = { aChangeServerState() }
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -92,14 +94,20 @@ class SearchAccountProviderPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - enter text one result with wellknown`() = runTest {
|
fun `present - enter text one result with wellknown`() = runTest {
|
||||||
val fakeWellknownRequest = FakeWellknownRequest()
|
val getWellKnownResult = lambdaRecorder<String, WellKnown> {
|
||||||
fakeWellknownRequest.givenResultMap(
|
when (it) {
|
||||||
mapOf(
|
"https://test.org" -> error("not found")
|
||||||
"https://test.io" to 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(
|
val presenter = SearchAccountProviderPresenter(
|
||||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRetriever),
|
||||||
changeServerPresenter = { aChangeServerState() }
|
changeServerPresenter = { aChangeServerState() }
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
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
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* 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.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
@ -20,7 +20,7 @@ import kotlinx.serialization.Serializable
|
||||||
* .
|
* .
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ElementWellKnown(
|
data class InternalElementWellKnown(
|
||||||
@SerialName("registration_helper_url")
|
@SerialName("registration_helper_url")
|
||||||
val registrationHelperUrl: String? = null,
|
val registrationHelperUrl: String? = null,
|
||||||
@SerialName("enforce_element_pro")
|
@SerialName("enforce_element_pro")
|
||||||
|
|
@ -5,9 +5,8 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* 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.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
@ -26,13 +25,9 @@ import kotlinx.serialization.Serializable
|
||||||
* .
|
* .
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class WellKnown(
|
data class InternalWellKnown(
|
||||||
@SerialName("m.homeserver")
|
@SerialName("m.homeserver")
|
||||||
val homeServer: WellKnownBaseConfig? = null,
|
val homeServer: InternalWellKnownBaseConfig? = null,
|
||||||
@SerialName("m.identity_server")
|
@SerialName("m.identity_server")
|
||||||
val identityServer: WellKnownBaseConfig? = null,
|
val identityServer: InternalWellKnownBaseConfig? = null,
|
||||||
) {
|
)
|
||||||
fun isValid(): Boolean {
|
|
||||||
return homeServer?.baseURL?.isNotBlank().orFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* 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.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
@ -20,7 +20,7 @@ import kotlinx.serialization.Serializable
|
||||||
* .
|
* .
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class WellKnownBaseConfig(
|
data class InternalWellKnownBaseConfig(
|
||||||
@SerialName("base_url")
|
@SerialName("base_url")
|
||||||
val baseURL: String? = null
|
val baseURL: String? = null
|
||||||
)
|
)
|
||||||
|
|
@ -5,14 +5,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* 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
|
import retrofit2.http.GET
|
||||||
|
|
||||||
internal interface WellknownAPI {
|
internal interface WellknownAPI {
|
||||||
@GET(".well-known/matrix/client")
|
@GET(".well-known/matrix/client")
|
||||||
suspend fun getWellKnown(): WellKnown
|
suspend fun getWellKnown(): InternalWellKnown
|
||||||
|
|
||||||
@GET(".well-known/element/element.json")
|
@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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue