diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt index c3989502541..ea063e8808c 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt @@ -24,6 +24,7 @@ import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.functional.Either import com.wire.kalium.network.exceptions.KaliumException import com.wire.kalium.network.exceptions.isFederationDenied +import com.wire.kalium.network.exceptions.isFederationNotEnabled import com.wire.kalium.network.utils.NetworkResponse import io.ktor.utils.io.errors.IOException import kotlinx.coroutines.flow.Flow @@ -147,6 +148,7 @@ sealed class NetworkFailure : CoreFailure { data class General(val label: String) : FederatedBackendFailure() data class FederationDenied(val label: String) : FederatedBackendFailure() + data class FederationNotEnabled(val label: String) : FederatedBackendFailure() data class ConflictingBackends(override val domains: List) : FederatedBackendFailure(), RetryableFailure @@ -200,6 +202,8 @@ internal inline fun wrapApiRequest(networkCall: () -> NetworkResponse< exception is KaliumException.FederationError -> { if (exception.isFederationDenied()) { Either.Left(NetworkFailure.FederatedBackendFailure.FederationDenied(exception.errorResponse.label)) + } else if (exception.isFederationNotEnabled()) { + Either.Left(NetworkFailure.FederatedBackendFailure.FederationNotEnabled(exception.errorResponse.label)) } else { Either.Left(NetworkFailure.FederatedBackendFailure.General(exception.errorResponse.label)) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt index 7593219e01f..2c31d043287 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/user/UserRepository.kt @@ -18,7 +18,9 @@ package com.wire.kalium.logic.data.user +import com.wire.kalium.logger.obfuscateDomain import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.NetworkFailure import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.data.conversation.MemberMapper import com.wire.kalium.logic.data.conversation.Recipient @@ -42,6 +44,7 @@ import com.wire.kalium.logic.failure.SelfUserDeleted import com.wire.kalium.logic.feature.SelfTeamIdProvider import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.functional.flatMap +import com.wire.kalium.logic.functional.flatMapLeft import com.wire.kalium.logic.functional.fold import com.wire.kalium.logic.functional.foldToEitherWhileRight import com.wire.kalium.logic.functional.getOrNull @@ -237,6 +240,24 @@ internal class UserDataSource internal constructor( ) } } + .flatMapLeft { error -> + if (error is NetworkFailure.FederatedBackendFailure.FederationNotEnabled) { + val domains = qualifiedUserIdList + .filterNot { it.domain == selfUserId.domain } + .map { it.domain.obfuscateDomain() } + .toSet() + val domainNames = domains.joinToString(separator = ", ") + kaliumLogger.e("User ids contains different domains when federation is not enabled by backend: $domainNames") + wrapApiRequest { + userDetailsApi.getMultipleUsers( + ListUserRequest.qualifiedIds(qualifiedUserIdList.filter { it.domain == selfUserId.domain } + .map { userId -> userId.toApi() }) + ) + } + } else { + Either.Left(error) + } + } .flatMap { listUserProfileDTO -> if (listUserProfileDTO.usersFailed.isNotEmpty()) { kaliumLogger.d("Handling ${listUserProfileDTO.usersFailed.size} failed users") diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt index b9363f70f23..f81e79d11d7 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/user/UserRepositoryTest.kt @@ -36,6 +36,7 @@ import com.wire.kalium.logic.framework.TestUser.LIST_USERS_DTO import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.functional.getOrNull import com.wire.kalium.logic.sync.receiver.UserEventReceiverTest +import com.wire.kalium.logic.test_util.TestNetworkException.federationNotEnabled import com.wire.kalium.logic.util.shouldFail import com.wire.kalium.logic.util.shouldSucceed import com.wire.kalium.network.api.base.authenticated.self.SelfApi @@ -198,6 +199,26 @@ class UserRepositoryTest { .wasNotInvoked() } + @Test + fun givenAnUserIdListWithDifferentDomain_whenApiReturnsFederationDisabledError_thenShouldTryToFetchOnlyUsersWithSelfDomain() = runTest { + // given + val requestedUserIds = setOf(TestUser.OTHER_USER_ID, TestUser.OTHER_FEDERATED_USER_ID) + val (arrangement, userRepository) = Arrangement() + .withGetMultipleUsersApiRequestFederationNotEnabledError() + .arrange() + // when + userRepository.fetchUsersByIds(requestedUserIds).shouldFail() + // then + verify(arrangement.userDetailsApi) + .suspendFunction(arrangement.userDetailsApi::getMultipleUsers) + .with(eq(QualifiedUserIdListRequest(requestedUserIds.map { it.toApi() }.toList()))) + .wasInvoked(exactly = once) + verify(arrangement.userDetailsApi) + .suspendFunction(arrangement.userDetailsApi::getMultipleUsers) + .with(eq(QualifiedUserIdListRequest(listOf(TestUser.OTHER_USER_ID.toApi())))) + .wasInvoked(exactly = once) + } + @Test fun givenAnEmptyUserIdListFromSameDomainAsSelf_whenFetchingUsers_thenShouldNotFetchMultipleUsersAndSucceed() = runTest { // given @@ -696,6 +717,13 @@ class UserRepositoryTest { .thenReturn(NetworkResponse.Success(result, mapOf(), HttpStatusCode.OK.value)) } + fun withGetMultipleUsersApiRequestFederationNotEnabledError() = apply { + given(userDetailsApi) + .suspendFunction(userDetailsApi::getMultipleUsers) + .whenInvokedWith(any()) + .thenReturn(NetworkResponse.Error(federationNotEnabled)) + } + fun withUpdateDisplayNameApiRequestResponse(response: NetworkResponse) = apply { given(selfApi) .suspendFunction(selfApi::updateSelf) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/test_util/TestNetworkException.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/test_util/TestNetworkException.kt index 850cafe01ec..23da8afe25f 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/test_util/TestNetworkException.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/test_util/TestNetworkException.kt @@ -130,6 +130,10 @@ object TestNetworkException { val guestLinkDisables = KaliumException.InvalidRequestError( ErrorResponse(409, "Guest links are disabled", "guest-links-disabled") ) + + val federationNotEnabled = KaliumException.FederationError( + ErrorResponse(400, "no federator configured", "federation-not-enabled") + ) } object TestNetworkResponseError { diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/model/ErrorResponse.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/model/ErrorResponse.kt index d8167d436de..3bfe1af82c7 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/model/ErrorResponse.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/base/model/ErrorResponse.kt @@ -28,7 +28,7 @@ data class ErrorResponse( @SerialName("label") val label: String, @SerialName("data") val cause: Cause? = null, ) { - fun isFederationError() = cause?.type == "federation" + fun isFederationError() = cause?.type == "federation" || label.contains("federation") } @Serializable diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/exceptions/KaliumException.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/exceptions/KaliumException.kt index 87575b423ae..df48be0e151 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/exceptions/KaliumException.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/exceptions/KaliumException.kt @@ -32,6 +32,7 @@ import com.wire.kalium.network.exceptions.NetworkErrorLabel.BLACKLISTED_EMAIL import com.wire.kalium.network.exceptions.NetworkErrorLabel.DOMAIN_BLOCKED_FOR_REGISTRATION import com.wire.kalium.network.exceptions.NetworkErrorLabel.FEDERATION_DENIED import com.wire.kalium.network.exceptions.NetworkErrorLabel.FEDERATION_FAILURE +import com.wire.kalium.network.exceptions.NetworkErrorLabel.FEDERATION_NOT_ENABLED import com.wire.kalium.network.exceptions.NetworkErrorLabel.GUEST_LINKS_DISABLED import com.wire.kalium.network.exceptions.NetworkErrorLabel.HANDLE_EXISTS import com.wire.kalium.network.exceptions.NetworkErrorLabel.INVALID_CODE @@ -224,3 +225,4 @@ val KaliumException.InvalidRequestError.authenticationCodeFailure: Authenticatio } fun KaliumException.FederationError.isFederationDenied() = errorResponse.label == FEDERATION_DENIED +fun KaliumException.FederationError.isFederationNotEnabled() = errorResponse.label == FEDERATION_NOT_ENABLED diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/exceptions/NetworkErrorLabel.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/exceptions/NetworkErrorLabel.kt index bba49a103d6..8159370cbef 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/exceptions/NetworkErrorLabel.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/exceptions/NetworkErrorLabel.kt @@ -42,15 +42,18 @@ internal object NetworkErrorLabel { const val MLS_KEY_PACKAGE_REF_NOT_FOUND = "mls-key-package-ref-not-found" const val MLS_MISSING_GROUP_INFO = "mls-missing-group-info" const val UNKNOWN_CLIENT = "unknown-client" - const val FEDERATION_FAILURE = "federation-remote-error" const val NOT_TEAM_MEMBER = "no-team-member" const val NO_CONVERSATION = "no-conversation" const val NO_CONVERSATION_CODE = "no-conversation-code" const val GUEST_LINKS_DISABLED = "guest-links-disabled" const val ACCESS_DENIED = "access-denied" const val WRONG_CONVERSATION_PASSWORD = "invalid-conversation-password" - const val FEDERATION_UNREACHABLE_DOMAINS = "federation-unreachable-domains-error" + + // Federation + const val FEDERATION_FAILURE = "federation-remote-error" const val FEDERATION_DENIED = "federation-denied" + const val FEDERATION_NOT_ENABLED = "federation-not-enabled" + const val FEDERATION_UNREACHABLE_DOMAINS = "federation-unreachable-domains-error" object KaliumCustom { const val MISSING_REFRESH_TOKEN = "missing-refresh_token"