From 5196e8e52be1c3da72e797f0d18ab37b69af981f Mon Sep 17 00:00:00 2001 From: Boris Safonov Date: Thu, 28 Dec 2023 12:07:16 +0200 Subject: [PATCH 1/5] feat: Indicate user with valid E2EI certificate --- .../conversation/MLSConversationRepository.kt | 40 ++++++ ...etMembersE2EICertificateStatusesUseCase.kt | 68 +++++++++ .../usecase/GetUserE2EICertificateUseCase.kt | 57 ++++++++ .../kalium/logic/feature/user/UserScope.kt | 23 ++- ...mbersE2EICertificateStatusesUseCaseTest.kt | 131 ++++++++++++++++++ ...GetUserE2eiCertificateStatusUseCaseTest.kt | 131 ++++++++++++++++++ .../MLSConversationRepositoryArrangement.kt | 18 +++ .../mls/PemCertificateDecoderArrangement.kt | 51 +++++++ .../wire/kalium/persistence/Conversations.sq | 11 ++ .../dao/conversation/ConversationDAO.kt | 2 + .../dao/conversation/ConversationDAOImpl.kt | 15 ++ 11 files changed, 543 insertions(+), 4 deletions(-) create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetMembersE2EICertificateStatusesUseCase.kt create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetUserE2EICertificateUseCase.kt create mode 100644 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt create mode 100644 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetUserE2eiCertificateStatusUseCaseTest.kt create mode 100644 logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/PemCertificateDecoderArrangement.kt diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt index 0fd402374d5..cc3faf60bb1 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt @@ -29,11 +29,13 @@ import com.wire.kalium.logic.NetworkFailure import com.wire.kalium.logic.data.client.MLSClientProvider import com.wire.kalium.logic.data.event.Event import com.wire.kalium.logic.data.event.Event.Conversation.MLSWelcome +import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.GroupID import com.wire.kalium.logic.data.id.IdMapper import com.wire.kalium.logic.data.id.QualifiedClientID import com.wire.kalium.logic.data.id.toApi import com.wire.kalium.logic.data.id.toCrypto +import com.wire.kalium.logic.data.id.toDao import com.wire.kalium.logic.data.id.toModel import com.wire.kalium.logic.data.keypackage.KeyPackageRepository import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeysMapper @@ -118,6 +120,8 @@ interface MLSConversationRepository { ): Either suspend fun getClientIdentity(clientId: ClientId): Either + suspend fun getUserIdentity(userId: UserId): Either> + suspend fun getMembersIdentities(conversationId: ConversationId): Either>> } private enum class CommitStrategy { @@ -551,6 +555,42 @@ internal class MLSConversationDataSource( } } + override suspend fun getUserIdentity(userId: UserId) = + wrapStorageRequest { conversationDAO.getMLSGroupIdByUserId(userId.toDao()) }.flatMap { mlsGroupId -> + mlsClientProvider.getMLSClient().flatMap { mlsClient -> + wrapMLSRequest { + mlsClient.getUserIdentities( + mlsGroupId, + listOf(userId.toCrypto()) + )[userId.value]!! + } + } + } + + override suspend fun getMembersIdentities(conversationId: ConversationId): Either>> = + wrapStorageRequest { + val mlsGroupIdAndUsers = conversationDAO.getMLSGroupIdAndUserIdsByConversationId(conversationId.toDao()) + val mlsGroupId = mlsGroupIdAndUsers.keys.first { it != null }!! + val userIds = mlsGroupIdAndUsers[mlsGroupId]!! + + mlsGroupId to userIds + }.flatMap { (mlsGroupId, userIds) -> + mlsClientProvider.getMLSClient().flatMap { mlsClient -> + wrapMLSRequest { + val userIdsAndIdentity = mutableMapOf>() + + mlsClient.getUserIdentities(mlsGroupId, userIds.map { it.toModel().toCrypto() }) + .forEach { (userIdValue, identities) -> + userIds.firstOrNull { it.value == userIdValue }?.also { + userIdsAndIdentity[it.toModel()] = identities + } + } + + userIdsAndIdentity + } + } + } + private suspend fun retryOnCommitFailure( groupID: GroupID, retryOnClientMismatch: Boolean = true, diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetMembersE2EICertificateStatusesUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetMembersE2EICertificateStatusesUseCase.kt new file mode 100644 index 00000000000..95106577037 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetMembersE2EICertificateStatusesUseCase.kt @@ -0,0 +1,68 @@ +/* + * Wire + * Copyright (C) 2023 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.feature.e2ei.usecase + +import com.wire.kalium.cryptography.WireIdentity +import com.wire.kalium.logic.data.conversation.MLSConversationRepository +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.feature.e2ei.CertificateStatus +import com.wire.kalium.logic.feature.e2ei.PemCertificateDecoder +import com.wire.kalium.logic.functional.fold + +/** + * This use case is used to get the e2ei certificates of all the users in Conversation. + * Return [Map] where keys are [UserId] and values - nullable [CertificateStatus] of corresponding user. + */ +interface GetMembersE2EICertificateStatusesUseCase { + suspend operator fun invoke(conversationId: ConversationId): Map +} + +class GetMembersE2EICertificateStatusesUseCaseImpl internal constructor( + private val mlsConversationRepository: MLSConversationRepository, + private val pemCertificateDecoder: PemCertificateDecoder +) : GetMembersE2EICertificateStatusesUseCase { + override suspend operator fun invoke(conversationId: ConversationId): Map = + mlsConversationRepository.getMembersIdentities(conversationId).fold( + { mapOf() }, + { + it.mapValues { (_, identities) -> + identities.getUserCertificateStatus(pemCertificateDecoder) + } + } + ) +} + +/** + * @return null if list is empty; + * [CertificateStatus.REVOKED] if any certificate is revoked; + * [CertificateStatus.EXPIRED] if any certificate is expired; + * [CertificateStatus.VALID] otherwise. + */ +fun List.getUserCertificateStatus(pemCertificateDecoder: PemCertificateDecoder): CertificateStatus? { + val certificates = this.map { pemCertificateDecoder.decode(it.certificate, it.status) } + return if (certificates.isEmpty()) { + null + } else if (certificates.any { it.status == CertificateStatus.REVOKED }) { + CertificateStatus.REVOKED + } else if (certificates.any { it.status == CertificateStatus.EXPIRED }) { + CertificateStatus.EXPIRED + } else { + CertificateStatus.VALID + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetUserE2EICertificateUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetUserE2EICertificateUseCase.kt new file mode 100644 index 00000000000..94efd0a2e1d --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetUserE2EICertificateUseCase.kt @@ -0,0 +1,57 @@ +/* + * Wire + * Copyright (C) 2023 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.feature.e2ei.usecase + +import com.wire.kalium.logic.data.conversation.MLSConversationRepository +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.feature.e2ei.CertificateStatus +import com.wire.kalium.logic.feature.e2ei.PemCertificateDecoder +import com.wire.kalium.logic.functional.fold + +/** + * This use case is used to get the e2ei certificate status of specific user + */ +interface GetUserE2eiCertificateStatusUseCase { + suspend operator fun invoke(userId: UserId): GetUserE2eiCertificateStatusResult +} + +class GetUserE2eiCertificateStatusUseCaseImpl internal constructor( + private val mlsConversationRepository: MLSConversationRepository, + private val pemCertificateDecoder: PemCertificateDecoder +) : GetUserE2eiCertificateStatusUseCase { + override suspend operator fun invoke(userId: UserId): GetUserE2eiCertificateStatusResult = + mlsConversationRepository.getUserIdentity(userId).fold( + { + GetUserE2eiCertificateStatusResult.Failure.NotActivated + }, + { identities -> + identities.getUserCertificateStatus(pemCertificateDecoder)?.let { + GetUserE2eiCertificateStatusResult.Success(it) + } ?: GetUserE2eiCertificateStatusResult.Failure.NotActivated + } + ) +} + +sealed class GetUserE2eiCertificateStatusResult { + class Success(val status: CertificateStatus) : GetUserE2eiCertificateStatusResult() + sealed class Failure : GetUserE2eiCertificateStatusResult() { + data object NotActivated : Failure() + } +} + + diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt index c5c065dd6b0..6c5d9957280 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt @@ -48,6 +48,10 @@ import com.wire.kalium.logic.feature.e2ei.usecase.EnrollE2EIUseCase import com.wire.kalium.logic.feature.e2ei.usecase.EnrollE2EIUseCaseImpl import com.wire.kalium.logic.feature.e2ei.usecase.GetE2eiCertificateUseCase import com.wire.kalium.logic.feature.e2ei.usecase.GetE2eiCertificateUseCaseImpl +import com.wire.kalium.logic.feature.e2ei.usecase.GetMembersE2EICertificateStatusesUseCase +import com.wire.kalium.logic.feature.e2ei.usecase.GetMembersE2EICertificateStatusesUseCaseImpl +import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusUseCase +import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusUseCaseImpl import com.wire.kalium.logic.feature.message.MessageSender import com.wire.kalium.logic.feature.publicuser.GetAllContactsUseCase import com.wire.kalium.logic.feature.publicuser.GetAllContactsUseCaseImpl @@ -113,10 +117,21 @@ class UserScope internal constructor( private val pemCertificateDecoderImpl by lazy { PemCertificateDecoderImpl() } val getPublicAsset: GetAvatarAssetUseCase get() = GetAvatarAssetUseCaseImpl(assetRepository, userRepository) val enrollE2EI: EnrollE2EIUseCase get() = EnrollE2EIUseCaseImpl(e2EIRepository) - val getE2EICertificate: GetE2eiCertificateUseCase get() = GetE2eiCertificateUseCaseImpl( - mlsConversationRepository = mlsConversationRepository, - pemCertificateDecoder = pemCertificateDecoderImpl - ) + val getE2EICertificate: GetE2eiCertificateUseCase + get() = GetE2eiCertificateUseCaseImpl( + mlsConversationRepository = mlsConversationRepository, + pemCertificateDecoder = pemCertificateDecoderImpl + ) + val getUserE2eiCertificateStatus: GetUserE2eiCertificateStatusUseCase + get() = GetUserE2eiCertificateStatusUseCaseImpl( + mlsConversationRepository = mlsConversationRepository, + pemCertificateDecoder = pemCertificateDecoderImpl + ) + val getMembersE2EICertificateStatuses: GetMembersE2EICertificateStatusesUseCase + get() = GetMembersE2EICertificateStatusesUseCaseImpl( + mlsConversationRepository = mlsConversationRepository, + pemCertificateDecoder = pemCertificateDecoderImpl + ) val deleteAsset: DeleteAssetUseCase get() = DeleteAssetUseCaseImpl(assetRepository) val setUserHandle: SetUserHandleUseCase get() = SetUserHandleUseCase(accountRepository, validateUserHandleUseCase, syncManager) val getAllKnownUsers: GetAllContactsUseCase get() = GetAllContactsUseCaseImpl(userRepository) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt new file mode 100644 index 00000000000..b79c61e24e9 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt @@ -0,0 +1,131 @@ +/* + * Wire + * Copyright (C) 2023 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.feature.e2ei + +import com.wire.kalium.cryptography.CryptoCertificateStatus +import com.wire.kalium.cryptography.WireIdentity +import com.wire.kalium.logic.MLSFailure +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.feature.e2ei.usecase.GetMembersE2EICertificateStatusesUseCaseImpl +import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.util.arrangement.mls.MLSConversationRepositoryArrangement +import com.wire.kalium.logic.util.arrangement.mls.MLSConversationRepositoryArrangementImpl +import com.wire.kalium.logic.util.arrangement.mls.PemCertificateDecoderArrangement +import com.wire.kalium.logic.util.arrangement.mls.PemCertificateDecoderArrangementImpl +import io.mockative.any +import io.mockative.eq +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class GetMembersE2EICertificateStatusesUseCaseTest { + + @Test + fun givenErrorOnGettingMembersIdentities_thenEmptyMapResult() = runTest { + val (_, getMembersE2EICertificateStatuses) = arrange { + withMembersIdentities(Either.Left(MLSFailure.WrongEpoch)) + } + + val result = getMembersE2EICertificateStatuses(conversationId) + + assertEquals(mapOf(), result) + } + + @Test + fun givenEmptyWireIdentityMap_thenNotActivatedResult() = runTest { + val (_, getMembersE2EICertificateStatuses) = arrange { + withMembersIdentities(Either.Right(mapOf())) + } + + val result = getMembersE2EICertificateStatuses(conversationId) + + assertEquals(mapOf(), result) + } + + @Test + fun givenOneWireIdentityExpiredForSomeUser_thenResultUsersStatusIsExpired() = runTest { + val (_, getMembersE2EICertificateStatuses) = arrange { + withMembersIdentities( + Either.Right( + mapOf( + userId to listOf( + WIRE_IDENTITY, + WIRE_IDENTITY.copy(status = CryptoCertificateStatus.EXPIRED) + ) + ) + ) + ) + } + + val result = getMembersE2EICertificateStatuses(conversationId) + + assertEquals(CertificateStatus.EXPIRED, result[userId]) + } + + @Test + fun givenOneWireIdentityRevokedForSomeUser_thenResultUsersStatusIsRevoked() = runTest { + val userId2 = userId.copy(value = "value_2") + val (_, getMembersE2EICertificateStatuses) = arrange { + withMembersIdentities( + Either.Right( + mapOf( + userId to listOf( + WIRE_IDENTITY, + WIRE_IDENTITY.copy(status = CryptoCertificateStatus.REVOKED) + ), + userId2 to listOf(WIRE_IDENTITY) + ) + ) + ) + } + + val result = getMembersE2EICertificateStatuses(conversationId) + + assertEquals(CertificateStatus.REVOKED, result[userId]) + assertEquals(CertificateStatus.VALID, result[userId2]) + } + + private class Arrangement(private val block: Arrangement.() -> Unit) : + MLSConversationRepositoryArrangement by MLSConversationRepositoryArrangementImpl(), + PemCertificateDecoderArrangement by PemCertificateDecoderArrangementImpl() { + + fun arrange() = run { + withPemCertificateDecode(E2EI_CERTIFICATE, any(), eq(CryptoCertificateStatus.VALID)) + withPemCertificateDecode(E2EI_CERTIFICATE.copy(status = CertificateStatus.EXPIRED), any(), eq(CryptoCertificateStatus.EXPIRED)) + withPemCertificateDecode(E2EI_CERTIFICATE.copy(status = CertificateStatus.REVOKED), any(), eq(CryptoCertificateStatus.REVOKED)) + + block() + this@Arrangement to GetMembersE2EICertificateStatusesUseCaseImpl( + mlsConversationRepository = mlsConversationRepository, + pemCertificateDecoder = pemCertificateDecoder + ) + } + } + + private companion object { + fun arrange(configuration: Arrangement.() -> Unit) = Arrangement(configuration).arrange() + + private val userId = UserId("value", "domain") + private val conversationId = ConversationId("conversation_value", "domain") + private val WIRE_IDENTITY = + WireIdentity("id", "user_handle", "User Test", "domain.com", "certificate", CryptoCertificateStatus.VALID) + private val E2EI_CERTIFICATE = + E2eiCertificate(issuer = "issue", status = CertificateStatus.VALID, serialNumber = "number", certificateDetail = "details") + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetUserE2eiCertificateStatusUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetUserE2eiCertificateStatusUseCaseTest.kt new file mode 100644 index 00000000000..be4913c2e85 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetUserE2eiCertificateStatusUseCaseTest.kt @@ -0,0 +1,131 @@ +/* + * Wire + * Copyright (C) 2023 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.feature.e2ei + +import com.wire.kalium.cryptography.CryptoCertificateStatus +import com.wire.kalium.cryptography.WireIdentity +import com.wire.kalium.logic.MLSFailure +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusResult +import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusUseCaseImpl +import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.util.arrangement.mls.MLSConversationRepositoryArrangement +import com.wire.kalium.logic.util.arrangement.mls.MLSConversationRepositoryArrangementImpl +import com.wire.kalium.logic.util.arrangement.mls.PemCertificateDecoderArrangement +import com.wire.kalium.logic.util.arrangement.mls.PemCertificateDecoderArrangementImpl +import io.mockative.any +import io.mockative.eq +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class GetUserE2eiCertificateStatusUseCaseTest { + + @Test + fun givenErrorOnGettingUserIdentity_thenNotActivatedResult() = runTest { + val (_, getUserE2eiCertificateStatus) = arrange { + withUserIdentity(Either.Left(MLSFailure.WrongEpoch)) + } + + val result = getUserE2eiCertificateStatus(userId) + + assertEquals(GetUserE2eiCertificateStatusResult.Failure.NotActivated, result) + } + + @Test + fun givenEmptyWireIdentityList_thenNotActivatedResult() = runTest { + val (_, getUserE2eiCertificateStatus) = arrange { + withUserIdentity(Either.Right(listOf())) + } + + val result = getUserE2eiCertificateStatus(userId) + + assertEquals(GetUserE2eiCertificateStatusResult.Failure.NotActivated, result) + } + + @Test + fun givenOneWireIdentityExpired_thenResultIsExpired() = runTest { + val (_, getUserE2eiCertificateStatus) = arrange { + withUserIdentity(Either.Right(listOf(WIRE_IDENTITY, WIRE_IDENTITY.copy(status = CryptoCertificateStatus.EXPIRED)))) + } + + val result = getUserE2eiCertificateStatus(userId) + + assertTrue { result is GetUserE2eiCertificateStatusResult.Success } + assertEquals(CertificateStatus.EXPIRED, (result as GetUserE2eiCertificateStatusResult.Success).status) + } + + @Test + fun givenOneWireIdentityRevoked_thenResultIsRevoked() = runTest { + val (_, getUserE2eiCertificateStatus) = arrange { + withUserIdentity(Either.Right(listOf(WIRE_IDENTITY, WIRE_IDENTITY.copy(status = CryptoCertificateStatus.REVOKED)))) + } + + val result = getUserE2eiCertificateStatus(userId) + + assertTrue { result is GetUserE2eiCertificateStatusResult.Success } + assertEquals(CertificateStatus.REVOKED, (result as GetUserE2eiCertificateStatusResult.Success).status) + } + + @Test + fun givenOneWireIdentityRevoked_thenResultIsRevoked2() = runTest { + val (_, getUserE2eiCertificateStatus) = arrange { + withUserIdentity( + Either.Right( + listOf( + WIRE_IDENTITY.copy(status = CryptoCertificateStatus.EXPIRED), + WIRE_IDENTITY.copy(status = CryptoCertificateStatus.REVOKED) + ) + ) + ) + } + + val result = getUserE2eiCertificateStatus(userId) + + assertTrue { result is GetUserE2eiCertificateStatusResult.Success } + assertEquals(CertificateStatus.REVOKED, (result as GetUserE2eiCertificateStatusResult.Success).status) + } + + private class Arrangement(private val block: Arrangement.() -> Unit) : + MLSConversationRepositoryArrangement by MLSConversationRepositoryArrangementImpl(), + PemCertificateDecoderArrangement by PemCertificateDecoderArrangementImpl() { + + fun arrange() = run { + withPemCertificateDecode(E2EI_CERTIFICATE, any(), eq(CryptoCertificateStatus.VALID)) + withPemCertificateDecode(E2EI_CERTIFICATE.copy(status = CertificateStatus.EXPIRED), any(), eq(CryptoCertificateStatus.EXPIRED)) + withPemCertificateDecode(E2EI_CERTIFICATE.copy(status = CertificateStatus.REVOKED), any(), eq(CryptoCertificateStatus.REVOKED)) + + block() + this@Arrangement to GetUserE2eiCertificateStatusUseCaseImpl( + mlsConversationRepository = mlsConversationRepository, + pemCertificateDecoder = pemCertificateDecoder + ) + } + } + + private companion object { + fun arrange(configuration: Arrangement.() -> Unit) = Arrangement(configuration).arrange() + + private val userId = UserId("value", "domain") + private val WIRE_IDENTITY = + WireIdentity("id", "user_handle", "User Test", "domain.com", "certificate", CryptoCertificateStatus.VALID) + private val E2EI_CERTIFICATE = + E2eiCertificate(issuer = "issue", status = CertificateStatus.VALID, serialNumber = "number", certificateDetail = "details") + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/MLSConversationRepositoryArrangement.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/MLSConversationRepositoryArrangement.kt index 7e35a0dc8f9..56271909fcd 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/MLSConversationRepositoryArrangement.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/MLSConversationRepositoryArrangement.kt @@ -17,8 +17,10 @@ */ package com.wire.kalium.logic.util.arrangement.mls +import com.wire.kalium.cryptography.WireIdentity import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.data.conversation.MLSConversationRepository +import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.functional.Either import io.mockative.any import io.mockative.given @@ -28,6 +30,8 @@ interface MLSConversationRepositoryArrangement { val mlsConversationRepository: MLSConversationRepository fun withIsGroupOutOfSync(result: Either) + fun withUserIdentity(result: Either>) + fun withMembersIdentities(result: Either>>) } class MLSConversationRepositoryArrangementImpl : MLSConversationRepositoryArrangement { @@ -39,4 +43,18 @@ class MLSConversationRepositoryArrangementImpl : MLSConversationRepositoryArrang .whenInvokedWith(any(), any()) .thenReturn(result) } + + override fun withUserIdentity(result: Either>) { + given(mlsConversationRepository) + .suspendFunction(mlsConversationRepository::getUserIdentity) + .whenInvokedWith(any()) + .thenReturn(result) + } + + override fun withMembersIdentities(result: Either>>) { + given(mlsConversationRepository) + .suspendFunction(mlsConversationRepository::getMembersIdentities) + .whenInvokedWith(any()) + .thenReturn(result) + } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/PemCertificateDecoderArrangement.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/PemCertificateDecoderArrangement.kt new file mode 100644 index 00000000000..4509f4906f8 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/PemCertificateDecoderArrangement.kt @@ -0,0 +1,51 @@ +/* + * Wire + * Copyright (C) 2023 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.util.arrangement.mls + +import com.wire.kalium.cryptography.CryptoCertificateStatus +import com.wire.kalium.logic.feature.e2ei.E2eiCertificate +import com.wire.kalium.logic.feature.e2ei.PemCertificateDecoder +import io.mockative.any +import io.mockative.given +import io.mockative.matchers.Matcher +import io.mockative.mock + +interface PemCertificateDecoderArrangement { + val pemCertificateDecoder: PemCertificateDecoder + + fun withPemCertificateDecode( + result: E2eiCertificate, + certificateMatcher: Matcher = any(), + statusMatcher: Matcher = any() + ) +} + +class PemCertificateDecoderArrangementImpl : PemCertificateDecoderArrangement { + override val pemCertificateDecoder: PemCertificateDecoder = mock(PemCertificateDecoder::class) + + override fun withPemCertificateDecode( + result: E2eiCertificate, + certificateMatcher: Matcher, + statusMatcher: Matcher + ) { + given(pemCertificateDecoder) + .function(pemCertificateDecoder::decode) + .whenInvokedWith(certificateMatcher, statusMatcher) + .thenReturn(result) + } +} diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq index 29e5f1697c2..c8473a7db0f 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq @@ -380,6 +380,17 @@ ON Member.conversation = Conversation.qualified_id OR Client.user_id = Conversat WHERE Conversation.mls_group_id IS NOT NULL AND Client.id = :clientId ORDER BY Conversation.type DESC LIMIT 1; +getMLSGroupIdByUserId: +SELECT Conversation.mls_group_id FROM Member +JOIN Conversation ON Conversation.qualified_id = Member.conversation +WHERE Conversation.type = 'ONE_ON_ONE' AND Member.user = :userId; + +getMLSGroupIdAndUsersByConversationId: +SELECT Conversation.mls_group_id, Member.user FROM Member +JOIN Conversation ON Conversation.qualified_id = Member.conversation +WHERE Conversation.qualified_id = :conversationId +GROUP BY Conversation.mls_group_id; + updateConversationReceiptMode: UPDATE Conversation SET receipt_mode = ? diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt index 15201be46d9..0f598b77f39 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt @@ -112,4 +112,6 @@ interface ConversationDAO { suspend fun updateLegalHoldStatusChangeNotified(conversationId: QualifiedIDEntity, notified: Boolean): Boolean suspend fun observeLegalHoldStatus(conversationId: QualifiedIDEntity): Flow suspend fun observeLegalHoldStatusChangeNotified(conversationId: QualifiedIDEntity): Flow + suspend fun getMLSGroupIdByUserId(userId: UserIDEntity): String? + suspend fun getMLSGroupIdAndUserIdsByConversationId(conversationId: QualifiedIDEntity): Map> } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt index 9badd4d15b1..18b011091bd 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt @@ -65,6 +65,21 @@ internal class ConversationDAOImpl internal constructor( .executeAsOneOrNull() } + override suspend fun getMLSGroupIdByUserId(userId: UserIDEntity): String? = + withContext(coroutineContext) { + conversationQueries.getMLSGroupIdByUserId(userId) + .executeAsOneOrNull() + ?.mls_group_id + } + + override suspend fun getMLSGroupIdAndUserIdsByConversationId(conversationId: QualifiedIDEntity): Map> = + withContext(coroutineContext) { + conversationQueries.getMLSGroupIdAndUsersByConversationId(conversationId) + .executeAsList() + .groupBy { it.mls_group_id } + .mapValues { it.value.map { value -> value.user } } + } + override suspend fun insertConversation(conversationEntity: ConversationEntity) = withContext(coroutineContext) { nonSuspendingInsertConversation(conversationEntity) } From 1ffe389a06c2ac25ba2be1dff793b36471fb94d7 Mon Sep 17 00:00:00 2001 From: Boris Safonov Date: Thu, 28 Dec 2023 15:55:43 +0200 Subject: [PATCH 2/5] Code style fix --- .../logic/feature/e2ei/usecase/GetUserE2EICertificateUseCase.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetUserE2EICertificateUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetUserE2EICertificateUseCase.kt index 94efd0a2e1d..b2afadda32a 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetUserE2EICertificateUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetUserE2EICertificateUseCase.kt @@ -53,5 +53,3 @@ sealed class GetUserE2eiCertificateStatusResult { data object NotActivated : Failure() } } - - From c6288313db2f63d5995d521b0d2d6320fdf67b16 Mon Sep 17 00:00:00 2001 From: Boris Safonov Date: Wed, 3 Jan 2024 20:48:11 +0200 Subject: [PATCH 3/5] feat: Indicate user with valid E2EI certificate: review comments --- .../conversation/MLSConversationRepository.kt | 22 ++++++++++--------- ...etMembersE2EICertificateStatusesUseCase.kt | 6 ++--- ...mbersE2EICertificateStatusesUseCaseTest.kt | 8 +++---- .../MLSConversationRepositoryArrangement.kt | 2 +- .../wire/kalium/persistence/Conversations.sq | 10 ++++----- .../dao/conversation/ConversationDAO.kt | 2 +- .../dao/conversation/ConversationDAOImpl.kt | 10 ++++----- 7 files changed, 29 insertions(+), 31 deletions(-) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt index cc3faf60bb1..3b6b98524d1 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt @@ -121,7 +121,10 @@ interface MLSConversationRepository { suspend fun getClientIdentity(clientId: ClientId): Either suspend fun getUserIdentity(userId: UserId): Either> - suspend fun getMembersIdentities(conversationId: ConversationId): Either>> + suspend fun getMembersIdentities( + conversationId: ConversationId, + userIds: List + ): Either>> } private enum class CommitStrategy { @@ -567,22 +570,21 @@ internal class MLSConversationDataSource( } } - override suspend fun getMembersIdentities(conversationId: ConversationId): Either>> = + override suspend fun getMembersIdentities( + conversationId: ConversationId, + userIds: List + ): Either>> = wrapStorageRequest { - val mlsGroupIdAndUsers = conversationDAO.getMLSGroupIdAndUserIdsByConversationId(conversationId.toDao()) - val mlsGroupId = mlsGroupIdAndUsers.keys.first { it != null }!! - val userIds = mlsGroupIdAndUsers[mlsGroupId]!! - - mlsGroupId to userIds - }.flatMap { (mlsGroupId, userIds) -> + conversationDAO.getMLSGroupIdByConversationId(conversationId.toDao())!! + }.flatMap { mlsGroupId -> mlsClientProvider.getMLSClient().flatMap { mlsClient -> wrapMLSRequest { val userIdsAndIdentity = mutableMapOf>() - mlsClient.getUserIdentities(mlsGroupId, userIds.map { it.toModel().toCrypto() }) + mlsClient.getUserIdentities(mlsGroupId, userIds.map { it.toCrypto() }) .forEach { (userIdValue, identities) -> userIds.firstOrNull { it.value == userIdValue }?.also { - userIdsAndIdentity[it.toModel()] = identities + userIdsAndIdentity[it] = identities } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetMembersE2EICertificateStatusesUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetMembersE2EICertificateStatusesUseCase.kt index 95106577037..55ee1e34d94 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetMembersE2EICertificateStatusesUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetMembersE2EICertificateStatusesUseCase.kt @@ -30,15 +30,15 @@ import com.wire.kalium.logic.functional.fold * Return [Map] where keys are [UserId] and values - nullable [CertificateStatus] of corresponding user. */ interface GetMembersE2EICertificateStatusesUseCase { - suspend operator fun invoke(conversationId: ConversationId): Map + suspend operator fun invoke(conversationId: ConversationId, userIds: List): Map } class GetMembersE2EICertificateStatusesUseCaseImpl internal constructor( private val mlsConversationRepository: MLSConversationRepository, private val pemCertificateDecoder: PemCertificateDecoder ) : GetMembersE2EICertificateStatusesUseCase { - override suspend operator fun invoke(conversationId: ConversationId): Map = - mlsConversationRepository.getMembersIdentities(conversationId).fold( + override suspend operator fun invoke(conversationId: ConversationId, userIds: List): Map = + mlsConversationRepository.getMembersIdentities(conversationId, userIds).fold( { mapOf() }, { it.mapValues { (_, identities) -> diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt index b79c61e24e9..b25f10619af 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt @@ -42,7 +42,7 @@ class GetMembersE2EICertificateStatusesUseCaseTest { withMembersIdentities(Either.Left(MLSFailure.WrongEpoch)) } - val result = getMembersE2EICertificateStatuses(conversationId) + val result = getMembersE2EICertificateStatuses(conversationId, listOf()) assertEquals(mapOf(), result) } @@ -53,7 +53,7 @@ class GetMembersE2EICertificateStatusesUseCaseTest { withMembersIdentities(Either.Right(mapOf())) } - val result = getMembersE2EICertificateStatuses(conversationId) + val result = getMembersE2EICertificateStatuses(conversationId, listOf()) assertEquals(mapOf(), result) } @@ -73,7 +73,7 @@ class GetMembersE2EICertificateStatusesUseCaseTest { ) } - val result = getMembersE2EICertificateStatuses(conversationId) + val result = getMembersE2EICertificateStatuses(conversationId, listOf(userId)) assertEquals(CertificateStatus.EXPIRED, result[userId]) } @@ -95,7 +95,7 @@ class GetMembersE2EICertificateStatusesUseCaseTest { ) } - val result = getMembersE2EICertificateStatuses(conversationId) + val result = getMembersE2EICertificateStatuses(conversationId, listOf(userId, userId2)) assertEquals(CertificateStatus.REVOKED, result[userId]) assertEquals(CertificateStatus.VALID, result[userId2]) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/MLSConversationRepositoryArrangement.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/MLSConversationRepositoryArrangement.kt index 56271909fcd..ec6aa181a3a 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/MLSConversationRepositoryArrangement.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/MLSConversationRepositoryArrangement.kt @@ -54,7 +54,7 @@ class MLSConversationRepositoryArrangementImpl : MLSConversationRepositoryArrang override fun withMembersIdentities(result: Either>>) { given(mlsConversationRepository) .suspendFunction(mlsConversationRepository::getMembersIdentities) - .whenInvokedWith(any()) + .whenInvokedWith(any(), any()) .thenReturn(result) } } diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq index c8473a7db0f..82804b13ee6 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq @@ -383,13 +383,11 @@ ORDER BY Conversation.type DESC LIMIT 1; getMLSGroupIdByUserId: SELECT Conversation.mls_group_id FROM Member JOIN Conversation ON Conversation.qualified_id = Member.conversation -WHERE Conversation.type = 'ONE_ON_ONE' AND Member.user = :userId; +WHERE Conversation.mls_group_id IS NOT NULL AND Member.user = :userId; -getMLSGroupIdAndUsersByConversationId: -SELECT Conversation.mls_group_id, Member.user FROM Member -JOIN Conversation ON Conversation.qualified_id = Member.conversation -WHERE Conversation.qualified_id = :conversationId -GROUP BY Conversation.mls_group_id; +getMLSGroupIdByConversationId: +SELECT Conversation.mls_group_id FROM Conversation +WHERE Conversation.qualified_id = :conversationId; updateConversationReceiptMode: UPDATE Conversation diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt index 0f598b77f39..6728e290163 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt @@ -113,5 +113,5 @@ interface ConversationDAO { suspend fun observeLegalHoldStatus(conversationId: QualifiedIDEntity): Flow suspend fun observeLegalHoldStatusChangeNotified(conversationId: QualifiedIDEntity): Flow suspend fun getMLSGroupIdByUserId(userId: UserIDEntity): String? - suspend fun getMLSGroupIdAndUserIdsByConversationId(conversationId: QualifiedIDEntity): Map> + suspend fun getMLSGroupIdByConversationId(conversationId: QualifiedIDEntity): String? } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt index 18b011091bd..5812106f1bf 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt @@ -69,15 +69,13 @@ internal class ConversationDAOImpl internal constructor( withContext(coroutineContext) { conversationQueries.getMLSGroupIdByUserId(userId) .executeAsOneOrNull() - ?.mls_group_id } - override suspend fun getMLSGroupIdAndUserIdsByConversationId(conversationId: QualifiedIDEntity): Map> = + override suspend fun getMLSGroupIdByConversationId(conversationId: QualifiedIDEntity): String? = withContext(coroutineContext) { - conversationQueries.getMLSGroupIdAndUsersByConversationId(conversationId) - .executeAsList() - .groupBy { it.mls_group_id } - .mapValues { it.value.map { value -> value.user } } + conversationQueries.getMLSGroupIdByConversationId(conversationId) + .executeAsOneOrNull() + ?.mls_group_id } override suspend fun insertConversation(conversationEntity: ConversationEntity) = withContext(coroutineContext) { From 5dea74cb95ea31679f675a10ceb2cf862cc3c2b0 Mon Sep 17 00:00:00 2001 From: Boris Safonov Date: Mon, 8 Jan 2024 12:55:29 +0200 Subject: [PATCH 4/5] Review updates --- .../MLSConversationRepositoryTest.kt | 86 +++++++++++++++++++ ...GetUserE2eiCertificateStatusUseCaseTest.kt | 10 +-- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt index 0db4a261d2f..8a6dfc6dec3 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt @@ -1265,6 +1265,71 @@ class MLSConversationRepositoryTest { .wasInvoked(once) } + @Test + fun givenUserId_whenGetMLSGroupIdByUserIdSucceed_thenReturnsIdentities() = runTest { + val groupId = "some_group" + val (arrangement, mlsConversationRepository) = Arrangement() + .withGetMLSClientSuccessful() + .withGetUserIdentitiesReturn( + mapOf( + TestUser.USER_ID.value to listOf(WIRE_IDENTITY), + "some_other_user_id" to listOf(WIRE_IDENTITY.copy(clientId = "another_client_id")), + ) + ) + .withGetMLSGroupIdByUserIdReturns(groupId) + .arrange() + + assertEquals(Either.Right(listOf(WIRE_IDENTITY)), mlsConversationRepository.getUserIdentity(TestUser.USER_ID)) + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::getUserIdentities) + .with(eq(groupId), any()) + .wasInvoked(once) + + verify(arrangement.conversationDAO) + .suspendFunction(arrangement.conversationDAO::getMLSGroupIdByUserId) + .with(any()) + .wasInvoked(once) + } + + @Test + fun givenConversationId_whenGetMLSGroupIdByConversationIdSucceed_thenReturnsIdentities() = runTest { + val groupId = "some_group" + val member1 = TestUser.USER_ID + val member2 = TestUser.USER_ID.copy(value = "member_2_id") + val member3 = TestUser.USER_ID.copy(value = "member_3_id") + val (arrangement, mlsConversationRepository) = Arrangement() + .withGetMLSClientSuccessful() + .withGetUserIdentitiesReturn( + mapOf( + member1.value to listOf(WIRE_IDENTITY), + member2.value to listOf(WIRE_IDENTITY.copy(clientId = "member_2_client_id")) + ) + ) + .withGetMLSGroupIdByConversationIdReturns(groupId) + .arrange() + + assertEquals( + Either.Right( + mapOf( + member1 to listOf(WIRE_IDENTITY), + member2 to listOf(WIRE_IDENTITY.copy(clientId = "member_2_client_id")) + ) + ), + mlsConversationRepository.getMembersIdentities(TestConversation.ID, listOf(member1, member2, member3)) + ) + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::getUserIdentities) + .with(eq(groupId), any()) + .wasInvoked(once) + + verify(arrangement.conversationDAO) + .suspendFunction(arrangement.conversationDAO::getMLSGroupIdByConversationId) + .with(any()) + .wasInvoked(once) + } + private class Arrangement { @Mock @@ -1512,6 +1577,27 @@ class MLSConversationRepositoryTest { .thenReturn(verificationStatus) } + fun withGetMLSGroupIdByUserIdReturns(result: String?) = apply { + given(conversationDAO) + .suspendFunction(conversationDAO::getMLSGroupIdByUserId) + .whenInvokedWith(anything()) + .thenReturn(result) + } + + fun withGetMLSGroupIdByConversationIdReturns(result: String?) = apply { + given(conversationDAO) + .suspendFunction(conversationDAO::getMLSGroupIdByConversationId) + .whenInvokedWith(anything()) + .thenReturn(result) + } + + fun withGetUserIdentitiesReturn(identitiesMap: Map>) = apply { + given(mlsClient) + .suspendFunction(mlsClient::getUserIdentities) + .whenInvokedWith(anything(), anything()) + .thenReturn(identitiesMap) + } + fun arrange() = this to MLSConversationDataSource( TestUser.SELF.id, keyPackageRepository, diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetUserE2eiCertificateStatusUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetUserE2eiCertificateStatusUseCaseTest.kt index be4913c2e85..a958bfa99ac 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetUserE2eiCertificateStatusUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetUserE2eiCertificateStatusUseCaseTest.kt @@ -38,7 +38,7 @@ import kotlin.test.assertTrue class GetUserE2eiCertificateStatusUseCaseTest { @Test - fun givenErrorOnGettingUserIdentity_thenNotActivatedResult() = runTest { + fun givenErrorOnGettingUserIdentity_whenGetUserE2eiCertificateStatus_thenNotActivatedResult() = runTest { val (_, getUserE2eiCertificateStatus) = arrange { withUserIdentity(Either.Left(MLSFailure.WrongEpoch)) } @@ -49,7 +49,7 @@ class GetUserE2eiCertificateStatusUseCaseTest { } @Test - fun givenEmptyWireIdentityList_thenNotActivatedResult() = runTest { + fun givenEmptyWireIdentityList_whenGetUserE2eiCertificateStatus_thenNotActivatedResult() = runTest { val (_, getUserE2eiCertificateStatus) = arrange { withUserIdentity(Either.Right(listOf())) } @@ -60,7 +60,7 @@ class GetUserE2eiCertificateStatusUseCaseTest { } @Test - fun givenOneWireIdentityExpired_thenResultIsExpired() = runTest { + fun givenOneWireIdentityExpired_whenGetUserE2eiCertificateStatus_thenResultIsExpired() = runTest { val (_, getUserE2eiCertificateStatus) = arrange { withUserIdentity(Either.Right(listOf(WIRE_IDENTITY, WIRE_IDENTITY.copy(status = CryptoCertificateStatus.EXPIRED)))) } @@ -72,7 +72,7 @@ class GetUserE2eiCertificateStatusUseCaseTest { } @Test - fun givenOneWireIdentityRevoked_thenResultIsRevoked() = runTest { + fun givenOneWireIdentityRevoked_whenGetUserE2eiCertificateStatus_thenResultIsRevoked() = runTest { val (_, getUserE2eiCertificateStatus) = arrange { withUserIdentity(Either.Right(listOf(WIRE_IDENTITY, WIRE_IDENTITY.copy(status = CryptoCertificateStatus.REVOKED)))) } @@ -84,7 +84,7 @@ class GetUserE2eiCertificateStatusUseCaseTest { } @Test - fun givenOneWireIdentityRevoked_thenResultIsRevoked2() = runTest { + fun givenOneWireIdentityRevoked_whenGetUserE2eiCertificateStatus_thenResultIsRevoked2() = runTest { val (_, getUserE2eiCertificateStatus) = arrange { withUserIdentity( Either.Right( From 4fb19083d22ecd175b8e0e32091be43fa9d4064f Mon Sep 17 00:00:00 2001 From: Boris Safonov Date: Wed, 10 Jan 2024 15:06:27 +0200 Subject: [PATCH 5/5] Review fixes --- .../e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt index b25f10619af..5cc8170eba8 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt @@ -37,7 +37,7 @@ import kotlin.test.assertEquals class GetMembersE2EICertificateStatusesUseCaseTest { @Test - fun givenErrorOnGettingMembersIdentities_thenEmptyMapResult() = runTest { + fun givenErrorOnGettingMembersIdentities_whenRequestMembersStatuses_thenEmptyMapResult() = runTest { val (_, getMembersE2EICertificateStatuses) = arrange { withMembersIdentities(Either.Left(MLSFailure.WrongEpoch)) } @@ -48,7 +48,7 @@ class GetMembersE2EICertificateStatusesUseCaseTest { } @Test - fun givenEmptyWireIdentityMap_thenNotActivatedResult() = runTest { + fun givenEmptyWireIdentityMap_whenRequestMembersStatuses_thenNotActivatedResult() = runTest { val (_, getMembersE2EICertificateStatuses) = arrange { withMembersIdentities(Either.Right(mapOf())) } @@ -59,7 +59,7 @@ class GetMembersE2EICertificateStatusesUseCaseTest { } @Test - fun givenOneWireIdentityExpiredForSomeUser_thenResultUsersStatusIsExpired() = runTest { + fun givenOneWireIdentityExpiredForSomeUser_whenRequestMembersStatuses_thenResultUsersStatusIsExpired() = runTest { val (_, getMembersE2EICertificateStatuses) = arrange { withMembersIdentities( Either.Right( @@ -79,7 +79,7 @@ class GetMembersE2EICertificateStatusesUseCaseTest { } @Test - fun givenOneWireIdentityRevokedForSomeUser_thenResultUsersStatusIsRevoked() = runTest { + fun givenOneWireIdentityRevokedForSomeUser_whenRequestMembersStatuses_thenResultUsersStatusIsRevoked() = runTest { val userId2 = userId.copy(value = "value_2") val (_, getMembersE2EICertificateStatuses) = arrange { withMembersIdentities(