Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new login use case chooser for context enterprise vs fallback (WPB-15966) #3287

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.wire.kalium.logic.feature.appVersioning.ObserveIfAppUpdateRequiredUse
import com.wire.kalium.logic.feature.auth.AddAuthenticatedUserUseCase
import com.wire.kalium.logic.feature.auth.AuthenticationScopeProvider
import com.wire.kalium.logic.feature.auth.LogoutCallbackManager
import com.wire.kalium.logic.feature.auth.ObserveLoginContextUseCase
import com.wire.kalium.logic.feature.auth.ValidateEmailUseCase
import com.wire.kalium.logic.feature.auth.ValidateEmailUseCaseImpl
import com.wire.kalium.logic.feature.auth.ValidatePasswordUseCase
Expand Down Expand Up @@ -103,7 +104,8 @@ class GlobalKaliumScope internal constructor(
userAgent,
kaliumConfigs.ignoreSSLCertificatesForUnboundCalls,
kaliumConfigs.certPinningConfig,
kaliumConfigs.mockedRequests?.let { MockUnboundNetworkClient.createMockEngine(it) }
kaliumConfigs.mockedRequests?.let { MockUnboundNetworkClient.createMockEngine(it) },
kaliumConfigs.developmentApiEnabled,
)
}

Expand All @@ -124,6 +126,7 @@ class GlobalKaliumScope internal constructor(

private val customServerConfigRepository: CustomServerConfigRepository
get() = CustomServerConfigDataSource(
unboundNetworkContainer.versionApi,
unboundNetworkContainer.serverConfigApi,
developmentApiEnabled = kaliumConfigs.developmentApiEnabled,
globalDatabase.serverConfigurationDAO
Expand All @@ -132,6 +135,7 @@ class GlobalKaliumScope internal constructor(
val validateSSOCodeUseCase: ValidateSSOCodeUseCase get() = ValidateSSOCodeUseCaseImpl()
val validateUserHandleUseCase: ValidateUserHandleUseCase get() = ValidateUserHandleUseCaseImpl()
val validatePasswordUseCase: ValidatePasswordUseCase get() = ValidatePasswordUseCaseImpl()
val observeLoginContext: ObserveLoginContextUseCase get() = ObserveLoginContextUseCase(customServerConfigRepository)

val addAuthenticatedAccount: AddAuthenticatedUserUseCase
get() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,25 @@
*/
package com.wire.kalium.logic.configuration.server

import com.benasher44.uuid.uuid4
import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.NetworkFailure
import com.wire.kalium.logic.StorageFailure
import com.wire.kalium.logic.di.MapperProvider
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.flatMap
import com.wire.kalium.logic.functional.fold
import com.wire.kalium.logic.functional.left
import com.wire.kalium.logic.functional.map
import com.wire.kalium.logic.wrapApiRequest
import com.wire.kalium.logic.wrapStorageRequest
import com.wire.kalium.network.BackendMetaDataUtil
import com.wire.kalium.network.BackendMetaDataUtilImpl
import com.wire.kalium.network.api.base.unbound.configuration.ServerConfigApi
import com.wire.kalium.network.api.base.unbound.versioning.VersionApi
import com.wire.kalium.network.api.unbound.versioning.VersionInfoDTO
import com.wire.kalium.persistence.daokaliumdb.ServerConfigurationDAO
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map

/**
Expand All @@ -58,15 +62,22 @@ internal interface CustomServerConfigRepository {
* updates lastBlackListCheckDate for the Set of configIds
*/
suspend fun updateAppBlackListCheckDate(configIds: Set<String>, date: String)

suspend fun observeServerConfigByLinks(links: ServerConfig.Links): Flow<Either<CoreFailure, ServerConfig>>
}

internal class CustomServerConfigDataSource internal constructor(
override val versionApi: VersionApi,
private val api: ServerConfigApi,
private val developmentApiEnabled: Boolean,
private val serverConfigurationDAO: ServerConfigurationDAO,
override val serverConfigurationDAO: ServerConfigurationDAO,
private val backendMetaDataUtil: BackendMetaDataUtil = BackendMetaDataUtilImpl,
private val serverConfigMapper: ServerConfigMapper = MapperProvider.serverConfigMapper()
) : CustomServerConfigRepository {
override val serverConfigMapper: ServerConfigMapper = MapperProvider.serverConfigMapper()
) : CustomServerConfigRepository, ServerConfigRepositoryExtension(
versionApi = versionApi,
serverConfigurationDAO = serverConfigurationDAO,
serverConfigMapper = serverConfigMapper
) {

override suspend fun fetchRemoteConfig(serverConfigUrl: String): Either<NetworkFailure, ServerConfig.Links> =
wrapApiRequest { api.fetchServerConfig(serverConfigUrl) }
Expand All @@ -89,47 +100,7 @@ internal class CustomServerConfigDataSource internal constructor(
}

override suspend fun storeConfig(links: ServerConfig.Links, metadata: ServerConfig.MetaData): Either<StorageFailure, ServerConfig> =
wrapStorageRequest {
// check if such config is already inserted
val storedConfigId = serverConfigurationDAO.configByLinks(serverConfigMapper.toEntity(links))?.id
if (storedConfigId != null) {
// if already exists then just update it
serverConfigurationDAO.updateServerMetaData(
id = storedConfigId,
federation = metadata.federation,
commonApiVersion = metadata.commonApiVersion.version
)
if (metadata.federation) serverConfigurationDAO.setFederationToTrue(storedConfigId)
storedConfigId
} else {
// otherwise insert new config
val newId = uuid4().toString()
serverConfigurationDAO.insert(
ServerConfigurationDAO.InsertData(
id = newId,
apiBaseUrl = links.api,
accountBaseUrl = links.accounts,
webSocketBaseUrl = links.webSocket,
blackListUrl = links.blackList,
teamsUrl = links.teams,
websiteUrl = links.website,
isOnPremises = links.isOnPremises,
title = links.title,
federation = metadata.federation,
domain = metadata.domain,
commonApiVersion = metadata.commonApiVersion.version,
apiProxyHost = links.apiProxy?.host,
apiProxyNeedsAuthentication = links.apiProxy?.needsAuthentication,
apiProxyPort = links.apiProxy?.port
)
)
newId
}
}.flatMap { storedConfigId ->
wrapStorageRequest { serverConfigurationDAO.configById(storedConfigId) }
}.map {
serverConfigMapper.fromEntity(it)
}
storeServerLinksAndMetadata(links, metadata)

override suspend fun getServerConfigsWithUserIdAfterTheDate(date: String): Either<StorageFailure, Flow<List<ServerConfigWithUserId>>> =
wrapStorageRequest { serverConfigurationDAO.getServerConfigsWithAccIdWithLastCheckBeforeDate(date) }
Expand All @@ -139,4 +110,12 @@ internal class CustomServerConfigDataSource internal constructor(
wrapStorageRequest { serverConfigurationDAO.updateBlackListCheckDate(configIds, date) }
}

override suspend fun observeServerConfigByLinks(links: ServerConfig.Links): Flow<Either<CoreFailure, ServerConfig>> =
fetchApiVersionAndStore(links).fold({
flowOf(it.left())
}) {
serverConfigurationDAO.getServerConfigByLinksFlow(serverConfigMapper.toEntity(links)).filterNotNull()
.map(serverConfigMapper::fromEntity)
.wrapStorageRequest()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,21 @@

package com.wire.kalium.logic.configuration.server

import com.benasher44.uuid.uuid4
import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.StorageFailure
import com.wire.kalium.logic.data.id.toDao
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.di.MapperProvider
import com.wire.kalium.logic.failure.ServerConfigFailure
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.flatMap
import com.wire.kalium.logic.functional.fold
import com.wire.kalium.logic.functional.map
import com.wire.kalium.logic.wrapApiRequest
import com.wire.kalium.logic.wrapStorageRequest
import com.wire.kalium.network.api.base.authenticated.UpgradePersonalToTeamApi.Companion.MIN_API_VERSION
import com.wire.kalium.network.api.base.unbound.versioning.VersionApi
import com.wire.kalium.network.api.unbound.configuration.ApiVersionDTO
import com.wire.kalium.persistence.daokaliumdb.ServerConfigurationDAO
import com.wire.kalium.util.KaliumDispatcher
import com.wire.kalium.util.KaliumDispatcherImpl
import io.ktor.http.Url
import kotlinx.coroutines.withContext

interface ServerConfigRepository {
Expand Down Expand Up @@ -71,77 +66,35 @@ interface ServerConfigRepository {

@Suppress("LongParameterList", "TooManyFunctions")
internal class ServerConfigDataSource(
private val dao: ServerConfigurationDAO,
private val versionApi: VersionApi,
private val serverConfigMapper: ServerConfigMapper = MapperProvider.serverConfigMapper(),
override val serverConfigurationDAO: ServerConfigurationDAO,
override val versionApi: VersionApi,
override val serverConfigMapper: ServerConfigMapper = MapperProvider.serverConfigMapper(),
private val dispatchers: KaliumDispatcher = KaliumDispatcherImpl
) : ServerConfigRepository {
) : ServerConfigRepository, ServerConfigRepositoryExtension(
versionApi = versionApi,
serverConfigurationDAO = serverConfigurationDAO,
serverConfigMapper = serverConfigMapper
) {

override val minimumApiVersionForPersonalToTeamAccountMigration = MIN_API_VERSION

override suspend fun getOrFetchMetadata(serverLinks: ServerConfig.Links): Either<CoreFailure, ServerConfig> =
wrapStorageRequest { dao.configByLinks(serverConfigMapper.toEntity(serverLinks)) }.fold({
wrapStorageRequest { serverConfigurationDAO.configByLinks(serverConfigMapper.toEntity(serverLinks)) }.fold({
fetchApiVersionAndStore(serverLinks)
}, {
Either.Right(serverConfigMapper.fromEntity(it))
})

override suspend fun storeConfig(links: ServerConfig.Links, metadata: ServerConfig.MetaData): Either<StorageFailure, ServerConfig> =
withContext(dispatchers.io) {
wrapStorageRequest {
// check if such config is already inserted
val storedConfigId = dao.configByLinks(serverConfigMapper.toEntity(links))?.id
if (storedConfigId != null) {
// if already exists then just update it
dao.updateServerMetaData(
id = storedConfigId,
federation = metadata.federation,
commonApiVersion = metadata.commonApiVersion.version
)
if (metadata.federation) dao.setFederationToTrue(storedConfigId)
storedConfigId
} else {
// otherwise insert new config
val newId = uuid4().toString()
dao.insert(
ServerConfigurationDAO.InsertData(
id = newId,
apiBaseUrl = links.api,
accountBaseUrl = links.accounts,
webSocketBaseUrl = links.webSocket,
blackListUrl = links.blackList,
teamsUrl = links.teams,
websiteUrl = links.website,
isOnPremises = links.isOnPremises,
title = links.title,
federation = metadata.federation,
domain = metadata.domain,
commonApiVersion = metadata.commonApiVersion.version,
apiProxyHost = links.apiProxy?.host,
apiProxyNeedsAuthentication = links.apiProxy?.needsAuthentication,
apiProxyPort = links.apiProxy?.port
)
)
newId
}
}.flatMap { storedConfigId ->
wrapStorageRequest { dao.configById(storedConfigId) }
}.map {
serverConfigMapper.fromEntity(it)
}
storeServerLinksAndMetadata(links, metadata)
}

override suspend fun fetchApiVersionAndStore(links: ServerConfig.Links): Either<CoreFailure, ServerConfig> =
fetchMetadata(links)
.flatMap { metaData ->
storeConfig(links, metaData)
}

override suspend fun updateConfigMetaData(serverConfig: ServerConfig): Either<CoreFailure, Unit> =
fetchMetadata(serverConfig.links)
.flatMap { newMetaData ->
wrapStorageRequest {
dao.updateServerMetaData(
serverConfigurationDAO.updateServerMetaData(
id = serverConfig.id,
federation = newMetaData.federation,
commonApiVersion = newMetaData.commonApiVersion.version
Expand All @@ -150,22 +103,12 @@ internal class ServerConfigDataSource(
}

override suspend fun configForUser(userId: UserId): Either<StorageFailure, ServerConfig> =
wrapStorageRequest { dao.configForUser(userId.toDao()) }
wrapStorageRequest { serverConfigurationDAO.configForUser(userId.toDao()) }
.map { serverConfigMapper.fromEntity(it) }

override suspend fun commonApiVersion(domain: String): Either<CoreFailure, Int> = wrapStorageRequest {
dao.getCommonApiVersion(domain)
serverConfigurationDAO.getCommonApiVersion(domain)
}

private suspend fun fetchMetadata(serverLinks: ServerConfig.Links): Either<CoreFailure, ServerConfig.MetaData> =
wrapApiRequest { versionApi.fetchApiVersion(Url(serverLinks.api)) }
.flatMap {
when (it.commonApiVersion) {
ApiVersionDTO.Invalid.New -> Either.Left(ServerConfigFailure.NewServerVersion)
ApiVersionDTO.Invalid.Unknown -> Either.Left(ServerConfigFailure.UnknownServerVersion)
is ApiVersionDTO.Valid -> Either.Right(it)
}
}.map { serverConfigMapper.fromDTO(it) }

override suspend fun getTeamUrlForUser(userId: UserId): String? = dao.teamUrlForUser(userId.toDao())
override suspend fun getTeamUrlForUser(userId: UserId): String? = serverConfigurationDAO.teamUrlForUser(userId.toDao())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Wire
* Copyright (C) 2025 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.configuration.server

import com.benasher44.uuid.uuid4
import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.StorageFailure
import com.wire.kalium.logic.failure.ServerConfigFailure
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.flatMap
import com.wire.kalium.logic.functional.map
import com.wire.kalium.logic.wrapApiRequest
import com.wire.kalium.logic.wrapStorageRequest
import com.wire.kalium.network.api.base.unbound.versioning.VersionApi
import com.wire.kalium.network.api.unbound.configuration.ApiVersionDTO
import com.wire.kalium.persistence.daokaliumdb.ServerConfigurationDAO
import io.ktor.http.Url

/**
* Common operations for the server configuration repository.
*/
internal abstract class ServerConfigRepositoryExtension(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We missed to cover this class with test

Copy link
Contributor Author

@yamilmedina yamilmedina Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, in fact these are covered by the classes extending this one ServerConfigDataSource and CustomServerConfigDataSource, so they are covered as can be seen here. https://app.codecov.io/gh/wireapp/kalium/pull/3287/blob/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepositoryExtension.kt

But, there is one actually missing for the new function in CustomServerConfigDataSource observeServerConfigByLinks I will add one to cover this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but it's unclear exactly what is being tested, we are only sure that the test runs through those lines of code.

We need to isolate the class during testing. This way, we can validate its behavior independently, without interference from external classes/dependencies, ensuring that it behaves correctly in all cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can take a look at a later point since this is the epic still, but the problem with this is, given is an abstract class, it is not straight forward to test, and the functions are covered indeed by the inherited testable classes

open val versionApi: VersionApi,
open val serverConfigurationDAO: ServerConfigurationDAO,
open val serverConfigMapper: ServerConfigMapper,
) {

suspend fun storeServerLinksAndMetadata(
links: ServerConfig.Links,
metadata: ServerConfig.MetaData
): Either<StorageFailure, ServerConfig> =
wrapStorageRequest {
// check if such config is already inserted
val storedConfigId = serverConfigurationDAO.configByLinks(serverConfigMapper.toEntity(links))?.id
if (storedConfigId != null) {
// if already exists then just update it
serverConfigurationDAO.updateServerMetaData(
id = storedConfigId,
federation = metadata.federation,
commonApiVersion = metadata.commonApiVersion.version
)
if (metadata.federation) serverConfigurationDAO.setFederationToTrue(storedConfigId)
storedConfigId
} else {
// otherwise insert new config
val newId = uuid4().toString()
serverConfigurationDAO.insert(
ServerConfigurationDAO.InsertData(
id = newId,
apiBaseUrl = links.api,
accountBaseUrl = links.accounts,
webSocketBaseUrl = links.webSocket,
blackListUrl = links.blackList,
teamsUrl = links.teams,
websiteUrl = links.website,
isOnPremises = links.isOnPremises,
title = links.title,
federation = metadata.federation,
domain = metadata.domain,
commonApiVersion = metadata.commonApiVersion.version,
apiProxyHost = links.apiProxy?.host,
apiProxyNeedsAuthentication = links.apiProxy?.needsAuthentication,
apiProxyPort = links.apiProxy?.port
)
)
newId
}
}.flatMap { storedConfigId ->
wrapStorageRequest { serverConfigurationDAO.configById(storedConfigId) }
}.map {
serverConfigMapper.fromEntity(it)
}

suspend fun fetchMetadata(serverLinks: ServerConfig.Links): Either<CoreFailure, ServerConfig.MetaData> =
wrapApiRequest { versionApi.fetchApiVersion(Url(serverLinks.api)) }
.flatMap {
when (it.commonApiVersion) {
ApiVersionDTO.Invalid.New -> Either.Left(ServerConfigFailure.NewServerVersion)
ApiVersionDTO.Invalid.Unknown -> Either.Left(ServerConfigFailure.UnknownServerVersion)
is ApiVersionDTO.Valid -> Either.Right(it)
}
}.map { serverConfigMapper.fromDTO(it) }

suspend fun fetchApiVersionAndStore(links: ServerConfig.Links): Either<CoreFailure, ServerConfig> =
fetchMetadata(links)
.flatMap { metaData ->
storeServerLinksAndMetadata(links, metaData)
}
}
Loading
Loading