From d77079f86dedd061efcd439a97bb83008aaaeb5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= <30429749+saleniuk@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:31:55 +0100 Subject: [PATCH 1/3] fix: handle with scheme and domain from corecrypto [WPB-7084] (#2624) --- .../com/wire/kalium/cryptography/IDs.kt | 7 ++- ...onversationsVerificationStatusesHandler.kt | 2 +- .../MLSConversationRepositoryTest.kt | 44 +++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/IDs.kt b/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/IDs.kt index 6cd88f9f7a1..b66f486fd60 100644 --- a/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/IDs.kt +++ b/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/IDs.kt @@ -72,7 +72,7 @@ data class CryptoQualifiedClientId( data class WireIdentity( val clientId: CryptoQualifiedClientId, - val handle: String, + val handle: String, // handle format is "{scheme}%40{handle}@{domain}", example: "wireapp://%40hans.wurst@elna.wire.link" val displayName: String, val domain: String, val certificate: String, @@ -80,7 +80,10 @@ data class WireIdentity( val thumbprint: String, val serialNumber: String, val endTimestampSeconds: Long -) +) { + val handleWithoutSchemeAtSignAndDomain: String + get() = handle.substringAfter("://%40").removeSuffix("@$domain") +} enum class CryptoCertificateStatus { VALID, EXPIRED, REVOKED; diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/MLSConversationsVerificationStatusesHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/MLSConversationsVerificationStatusesHandler.kt index 0fda5da96d6..4bad97b793d 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/MLSConversationsVerificationStatusesHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/MLSConversationsVerificationStatusesHandler.kt @@ -123,7 +123,7 @@ internal class MLSConversationsVerificationStatusesHandlerImpl( val isUserVerified = wireIdentity.firstOrNull { it.status != CryptoCertificateStatus.VALID || it.displayName != persistedMemberInfo?.name || - it.handle != persistedMemberInfo.handle + it.handleWithoutSchemeAtSignAndDomain != persistedMemberInfo.handle } == null if (!isUserVerified) { newStatus = VerificationStatus.NOT_VERIFIED 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 760bf9381fe..0ee1fb2eb9e 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 @@ -1521,6 +1521,50 @@ class MLSConversationRepositoryTest { .wasInvoked(once) } + @Test + fun givenHandleWithSchemeAndDomain_whenGetUserIdentity_thenHandleWithoutSchemeAtSignAndDomainShouldReturnProperValue() = runTest { + // given + val handleWithSchemeAndDomain = "wireapp://%40handle@domain.com" + val handle = "handle" + val groupId = Arrangement.GROUP_ID.value + val (_, mlsConversationRepository) = Arrangement() + .withGetEstablishedSelfMLSGroupIdReturns(groupId) + .withGetMLSClientSuccessful() + .withGetUserIdentitiesReturn(mapOf(groupId to listOf(WIRE_IDENTITY.copy(handle = handleWithSchemeAndDomain)))) + .arrange() + // when + val result = mlsConversationRepository.getUserIdentity(TestUser.USER_ID) + // then + result.shouldSucceed() { + it.forEach { + assertEquals(handle, it.handleWithoutSchemeAtSignAndDomain) + } + } + } + + @Test + fun givenHandleWithSchemeAndDomain_whenGetMemberIdentities_thenHandleWithoutSchemeAtSignAndDomainShouldReturnProperValue() = runTest { + // given + val handleWithSchemeAndDomain = "wireapp://%40handle@domain.com" + val handle = "handle" + val groupId = Arrangement.GROUP_ID.value + val (_, mlsConversationRepository) = Arrangement() + .withGetMLSGroupIdByConversationIdReturns(groupId) + .withGetMLSClientSuccessful() + .withGetUserIdentitiesReturn(mapOf(groupId to listOf(WIRE_IDENTITY.copy(handle = handleWithSchemeAndDomain)))) + .arrange() + // when + val result = mlsConversationRepository.getMembersIdentities(TestConversation.ID, listOf(TestUser.USER_ID)) + // then + result.shouldSucceed() { + it.values.forEach { + it.forEach { + assertEquals(handle, it.handleWithoutSchemeAtSignAndDomain) + } + } + } + } + private class Arrangement { @Mock From 79a7a573b2fd794a2827862060083f4f7df17b2f Mon Sep 17 00:00:00 2001 From: Vitor Hugo Schwaab Date: Mon, 11 Mar 2024 19:15:50 +0100 Subject: [PATCH 2/3] feat(MLS): allow creating mls conversations with partial success [WPB-3694] (#2623) When calling `establishMLSGroup` inside `MLSConversationRepository`, an additional parameter called `allowSkippingUsersWithoutKeyPackages` can be passed. It's `false` by default, and the only place where it is using `true` is when creating a group conversation. --- .../ConversationGroupRepository.kt | 21 ++--- .../JoinExistingMLSConversationUseCase.kt | 5 +- .../conversation/MLSConversationRepository.kt | 82 +++++++++++++++---- .../NewConversationMembersRepository.kt | 1 + ...ageClaimResult.kt => KeyPackageResults.kt} | 5 ++ .../ConversationGroupRepositoryTest.kt | 53 +++++++++++- .../MLSConversationRepositoryTest.kt | 53 ++++++++++++ .../JoinExistingMLSConversationUseCaseTest.kt | 10 +-- .../feature/mlsmigration/MLSMigratorTest.kt | 10 ++- 9 files changed, 199 insertions(+), 41 deletions(-) rename logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/mls/{KeyPackageClaimResult.kt => KeyPackageResults.kt} (90%) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepository.kt index 0a7eafa74f8..f87c2dbd591 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepository.kt @@ -144,17 +144,18 @@ internal class ConversationGroupRepositoryImpl( }.flatMap { newGroupConversationSystemMessagesCreator.value.conversationStarted(conversationEntity) }.flatMap { - newConversationMembersRepository.persistMembersAdditionToTheConversation( - conversationEntity.id, conversationResponse, failedUsersList - ).flatMap { - when (protocol) { - is Conversation.ProtocolInfo.Proteus -> Either.Right(Unit) - is Conversation.ProtocolInfo.MLSCapable -> mlsConversationRepository.establishMLSGroup( - groupID = protocol.groupId, - members = usersList + selfUserId - ) - } + when (protocol) { + is Conversation.ProtocolInfo.Proteus -> Either.Right(setOf()) + is Conversation.ProtocolInfo.MLSCapable -> mlsConversationRepository.establishMLSGroup( + groupID = protocol.groupId, + members = usersList + selfUserId, + allowSkippingUsersWithoutKeyPackages = true + ).map { it.notAddedUsers } } + }.flatMap { additionalFailedUsers -> + newConversationMembersRepository.persistMembersAdditionToTheConversation( + conversationEntity.id, conversationResponse, failedUsersList + additionalFailedUsers + ) }.flatMap { wrapStorageRequest { newGroupConversationSystemMessagesCreator.value.conversationStartedUnverifiedWarning( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/JoinExistingMLSConversationUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/JoinExistingMLSConversationUseCase.kt index 71fd713cb9d..1b6157dc0f8 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/JoinExistingMLSConversationUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/JoinExistingMLSConversationUseCase.kt @@ -31,6 +31,7 @@ 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.getOrElse +import com.wire.kalium.logic.functional.map import com.wire.kalium.logic.functional.onSuccess import com.wire.kalium.logic.kaliumLogger import com.wire.kalium.logic.logStructuredJson @@ -194,7 +195,7 @@ internal class JoinExistingMLSConversationUseCaseImpl( "protocolInfo" to conversation.protocol.toLogMap(), ) ) - } + }.map { Unit } } type == Conversation.Type.ONE_ON_ONE -> { @@ -214,7 +215,7 @@ internal class JoinExistingMLSConversationUseCaseImpl( "protocolInfo" to conversation.protocol.toLogMap(), ) ) - } + }.map { Unit } } else -> Either.Right(Unit) 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 d91c642bdf2..2fc30eadc14 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 @@ -33,6 +33,7 @@ import com.wire.kalium.logic.MLSFailure import com.wire.kalium.logic.NetworkFailure import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.data.client.MLSClientProvider +import com.wire.kalium.logic.data.conversation.mls.MLSAdditionResult import com.wire.kalium.logic.data.e2ei.CertificateRevocationListRepository import com.wire.kalium.logic.data.event.Event import com.wire.kalium.logic.data.event.Event.Conversation.MLSWelcome @@ -118,7 +119,28 @@ data class E2EIdentity( @Suppress("TooManyFunctions", "LongParameterList") interface MLSConversationRepository { suspend fun decryptMessage(message: ByteArray, groupID: GroupID): Either> - suspend fun establishMLSGroup(groupID: GroupID, members: List): Either + + /** + * Establishes an MLS (Messaging Layer Security) group with the specified group ID and members. + * + * Allows partial addition of members through the [allowSkippingUsersWithoutKeyPackages] parameter. + * If this parameter is set to true, users without key packages will be ignored and the rest will be added to the group. + * + * @param groupID The ID of the group to be established. Must be of type [GroupID]. + * @param members The list of user IDs (of type [UserId]) to be added as members to the group. + * @param allowSkippingUsersWithoutKeyPackages Flag indicating whether to allow a partial member list in case of some users + * not having key packages available. Default value is false. If false, will return [Either.Left] containing + * [CoreFailure.MissingKeyPackages] for the missing users. + * @return An instance of [Either] indicating the result of the operation. It can be either [Either.Right] if the + * group was successfully established, or [Either.Left] if an error occurred. If successful, returns [Unit]. + * Possible types of [Either.Left] are defined in the sealed interface [CoreFailure]. + */ + suspend fun establishMLSGroup( + groupID: GroupID, + members: List, + allowSkippingUsersWithoutKeyPackages: Boolean = false + ): Either + suspend fun establishMLSSubConversationGroup(groupID: GroupID, parentId: ConversationId): Either suspend fun establishMLSGroupFromWelcome(welcomeEvent: MLSWelcome): Either suspend fun hasEstablishedMLSGroup(groupID: GroupID): Either @@ -446,17 +468,23 @@ internal class MLSConversationDataSource( ) override suspend fun addMemberToMLSGroup(groupID: GroupID, userIdList: List): Either = - internalAddMemberToMLSGroup(groupID, userIdList, retryOnStaleMessage = true) + internalAddMemberToMLSGroup( + groupID = groupID, + userIdList = userIdList, + retryOnStaleMessage = true, + allowPartialMemberList = false + ).map { Unit } private suspend fun internalAddMemberToMLSGroup( groupID: GroupID, userIdList: List, - retryOnStaleMessage: Boolean - ): Either = withContext(serialDispatcher) { + retryOnStaleMessage: Boolean, + allowPartialMemberList: Boolean = false, + ): Either = withContext(serialDispatcher) { commitPendingProposals(groupID).flatMap { - produceAndSendCommitWithRetry(groupID, retryOnStaleMessage = retryOnStaleMessage) { + produceAndSendCommitWithRetryAndResult(groupID, retryOnStaleMessage = retryOnStaleMessage) { keyPackageRepository.claimKeyPackages(userIdList).flatMap { result -> - if (result.usersWithoutKeyPackagesAvailable.isNotEmpty()) { + if (result.usersWithoutKeyPackagesAvailable.isNotEmpty() && !allowPartialMemberList) { Either.Left(CoreFailure.MissingKeyPackages(result.usersWithoutKeyPackagesAvailable)) } else { Either.Right(result) @@ -464,7 +492,6 @@ internal class MLSConversationDataSource( }.flatMap { result -> val keyPackages = result.successfullyFetchedKeyPackages val clientKeyPackageList = keyPackages.map { it.keyPackage.decodeBase64Bytes() } - wrapMLSRequest { if (userIdList.isEmpty()) { // We are creating a group with only our self client which technically @@ -478,6 +505,12 @@ internal class MLSConversationDataSource( commitBundle?.crlNewDistributionPoints?.let { revocationList -> checkRevocationList(revocationList) } + }.map { + val additionResult = MLSAdditionResult( + result.successfullyFetchedKeyPackages.map { user -> UserId(user.userId, user.domain) }.toSet(), + result.usersWithoutKeyPackagesAvailable.toSet() + ) + CommitOperationResult(it, additionResult) } } } @@ -535,11 +568,17 @@ internal class MLSConversationDataSource( override suspend fun establishMLSGroup( groupID: GroupID, - members: List - ): Either = withContext(serialDispatcher) { + members: List, + allowSkippingUsersWithoutKeyPackages: Boolean, + ): Either = withContext(serialDispatcher) { mlsPublicKeysRepository.getKeys().flatMap { publicKeys -> val keys = publicKeys.map { mlsPublicKeysMapper.toCrypto(it) } - establishMLSGroup(groupID, members, keys) + establishMLSGroup( + groupID = groupID, + members = members, + keys = keys, + allowPartialMemberList = allowSkippingUsersWithoutKeyPackages + ) } } @@ -550,7 +589,12 @@ internal class MLSConversationDataSource( mlsClientProvider.getMLSClient().flatMap { mlsClient -> conversationDAO.getMLSGroupIdByConversationId(parentId.toDao())?.let { parentGroupId -> val externalSenderKey = mlsClient.getExternalSenders(GroupID(parentGroupId).toCrypto()) - establishMLSGroup(groupID, emptyList(), listOf(mlsPublicKeysMapper.toCrypto(externalSenderKey))) + establishMLSGroup( + groupID = groupID, + members = emptyList(), + keys = listOf(mlsPublicKeysMapper.toCrypto(externalSenderKey)), + allowPartialMemberList = false + ).map { Unit } } ?: Either.Left(StorageFailure.DataNotFound) } } @@ -558,8 +602,9 @@ internal class MLSConversationDataSource( private suspend fun establishMLSGroup( groupID: GroupID, members: List, - keys: List - ): Either = withContext(serialDispatcher) { + keys: List, + allowPartialMemberList: Boolean = false, + ): Either = withContext(serialDispatcher) { mlsClientProvider.getMLSClient().flatMap { mlsClient -> wrapMLSRequest { mlsClient.createConversation( @@ -573,18 +618,23 @@ internal class MLSConversationDataSource( Either.Left(it) } }.flatMap { - internalAddMemberToMLSGroup(groupID, members, retryOnStaleMessage = false).onFailure { + internalAddMemberToMLSGroup( + groupID = groupID, + userIdList = members, + retryOnStaleMessage = false, + allowPartialMemberList = allowPartialMemberList + ).onFailure { wrapMLSRequest { mlsClient.wipeConversation(groupID.toCrypto()) } } - }.flatMap { + }.flatMap { additionResult -> wrapStorageRequest { conversationDAO.updateConversationGroupState( ConversationEntity.GroupState.ESTABLISHED, idMapper.toGroupIDEntity(groupID) ) - } + }.map { additionResult } } } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/NewConversationMembersRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/NewConversationMembersRepository.kt index b40971b82d0..025ec0af963 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/NewConversationMembersRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/NewConversationMembersRepository.kt @@ -34,6 +34,7 @@ import com.wire.kalium.persistence.dao.member.MemberDAO * Either all users are added or some of them could fail to be added. */ internal interface NewConversationMembersRepository { + // TODO(refactor): Use Set instead of List to avoid duplications suspend fun persistMembersAdditionToTheConversation( conversationId: ConversationIDEntity, conversationResponse: ConversationResponse, diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/mls/KeyPackageClaimResult.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/mls/KeyPackageResults.kt similarity index 90% rename from logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/mls/KeyPackageClaimResult.kt rename to logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/mls/KeyPackageResults.kt index 694272c5a4d..fed6ea828d6 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/mls/KeyPackageClaimResult.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/mls/KeyPackageResults.kt @@ -24,3 +24,8 @@ data class KeyPackageClaimResult( val successfullyFetchedKeyPackages: List, val usersWithoutKeyPackagesAvailable: Set ) + +data class MLSAdditionResult( + val successfullyAddedUsers: Set, + val notAddedUsers: Set +) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepositoryTest.kt index 818807db2e2..6025b53873a 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepositoryTest.kt @@ -22,6 +22,7 @@ import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.MLSFailure import com.wire.kalium.logic.NetworkFailure import com.wire.kalium.logic.StorageFailure +import com.wire.kalium.logic.data.conversation.mls.MLSAdditionResult import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.GroupID import com.wire.kalium.logic.data.id.SelfTeamIdProvider @@ -255,7 +256,7 @@ class ConversationGroupRepositoryTest { .withCreateNewConversationAPIResponses(arrayOf(NetworkResponse.Success(conversationResponse, emptyMap(), 201))) .withSelfTeamId(Either.Right(TestUser.SELF.teamId)) .withInsertConversationSuccess() - .withMlsConversationEstablished() + .withMlsConversationEstablished(MLSAdditionResult(setOf(TestUser.USER_ID), emptySet())) .withConversationDetailsById(TestConversation.GROUP_VIEW_ENTITY(PROTEUS_PROTOCOL_INFO)) .withSuccessfulNewConversationGroupStartedHandled() .withSuccessfulNewConversationMemberHandled() @@ -278,7 +279,7 @@ class ConversationGroupRepositoryTest { verify(mlsConversationRepository) .suspendFunction(mlsConversationRepository::establishMLSGroup) - .with(anything(), anything()) + .with(anything(), anything(), eq(true)) .wasInvoked(once) verify(newConversationMembersRepository) @@ -288,6 +289,50 @@ class ConversationGroupRepositoryTest { } } + @Test + fun givenMLSProtocolIsUsedAndSomeUsersAreNotAddedToMLSGroup_whenCallingCreateGroupConversation_thenMissingMembersArePersisted() = + runTest { + val conversationResponse = CONVERSATION_RESPONSE.copy(protocol = MLS) + val missingMembersFromMLSGroup = setOf(TestUser.OTHER_USER_ID, TestUser.OTHER_USER_ID_2) + val successfullyAddedUsers = setOf(TestUser.USER_ID) + val allWantedMembers = successfullyAddedUsers + missingMembersFromMLSGroup + val (arrangement, conversationGroupRepository) = Arrangement() + .withCreateNewConversationAPIResponses(arrayOf(NetworkResponse.Success(conversationResponse, emptyMap(), 201))) + .withSelfTeamId(Either.Right(TestUser.SELF.teamId)) + .withInsertConversationSuccess() + .withMlsConversationEstablished(MLSAdditionResult(setOf(TestUser.USER_ID), notAddedUsers = missingMembersFromMLSGroup)) + .withConversationDetailsById(TestConversation.GROUP_VIEW_ENTITY(PROTEUS_PROTOCOL_INFO)) + .withSuccessfulNewConversationGroupStartedHandled() + .withSuccessfulNewConversationMemberHandled() + .withSuccessfulNewConversationGroupStartedUnverifiedWarningHandled() + .arrange() + + val result = conversationGroupRepository.createGroupConversation( + GROUP_NAME, + allWantedMembers.toList(), + ConversationOptions(protocol = ConversationOptions.Protocol.MLS) + ) + + result.shouldSucceed() + + with(arrangement) { + verify(conversationDAO) + .suspendFunction(conversationDAO::insertConversation) + .with(anything()) + .wasInvoked(once) + + verify(mlsConversationRepository) + .suspendFunction(mlsConversationRepository::establishMLSGroup) + .with(anything(), anything(), eq(true)) + .wasInvoked(once) + + verify(newConversationMembersRepository) + .suspendFunction(newConversationMembersRepository::persistMembersAdditionToTheConversation) + .with(anything(), anything(), eq(missingMembersFromMLSGroup.toList())) + .wasInvoked(once) + } + } + @Test fun givenProteusConversation_whenAddingMembersToConversation_thenShouldSucceed() = runTest { val (arrangement, conversationGroupRepository) = Arrangement() @@ -1366,11 +1411,11 @@ class ConversationGroupRepositoryTest { selfTeamIdProvider ) - fun withMlsConversationEstablished(): Arrangement { + fun withMlsConversationEstablished(additionResult: MLSAdditionResult): Arrangement { given(mlsConversationRepository) .suspendFunction(mlsConversationRepository::establishMLSGroup) .whenInvokedWith(anything(), anything()) - .thenReturn(Either.Right(Unit)) + .thenReturn(Either.Right(additionResult)) return this } 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 0ee1fb2eb9e..838c482afaa 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 @@ -39,6 +39,7 @@ import com.wire.kalium.logic.data.client.MLSClientProvider import com.wire.kalium.logic.data.conversation.MLSConversationRepositoryTest.Arrangement.Companion.COMMIT_BUNDLE import com.wire.kalium.logic.data.conversation.MLSConversationRepositoryTest.Arrangement.Companion.CRYPTO_CLIENT_ID import com.wire.kalium.logic.data.conversation.MLSConversationRepositoryTest.Arrangement.Companion.E2EI_CONVERSATION_CLIENT_INFO_ENTITY +import com.wire.kalium.logic.data.conversation.MLSConversationRepositoryTest.Arrangement.Companion.KEY_PACKAGE import com.wire.kalium.logic.data.conversation.MLSConversationRepositoryTest.Arrangement.Companion.ROTATE_BUNDLE import com.wire.kalium.logic.data.conversation.MLSConversationRepositoryTest.Arrangement.Companion.TEST_FAILURE import com.wire.kalium.logic.data.conversation.MLSConversationRepositoryTest.Arrangement.Companion.WIRE_IDENTITY @@ -244,6 +245,58 @@ class MLSConversationRepositoryTest { .wasInvoked(once) } + @Test + fun givenPartialKeyClaimingResponsesAndAllowPartial_whenCallingEstablishMLSGroup_thenPartialGroupCreatedAndSuccessReturned() = runTest { + val userMissingKeyPackage = TestUser.USER_ID.copy(value = "missingKP") + val userWithKeyPackage = TestConversation.USER_1 + val usersMissingKeyPackages = setOf(userMissingKeyPackage) + val usersWithKeyPackages = setOf(userWithKeyPackage) + val keyPackageSuccess = KEY_PACKAGE.copy(userId = userWithKeyPackage.value, domain = userWithKeyPackage.domain) + val (arrangement, mlsConversationRepository) = Arrangement() + .withCommitPendingProposalsReturningNothing() + .withClaimKeyPackagesSuccessful(keyPackages = listOf(keyPackageSuccess), usersWithoutKeyPackages = usersMissingKeyPackages) + .withGetMLSClientSuccessful() + .withGetPublicKeysSuccessful() + .withAddMLSMemberSuccessful() + .withSendCommitBundleSuccessful() + .arrange() + + val result = mlsConversationRepository.establishMLSGroup( + Arrangement.GROUP_ID, + (usersWithKeyPackages + userMissingKeyPackage).toList(), + allowSkippingUsersWithoutKeyPackages = true + ) + result.shouldSucceed { + assertEquals(usersMissingKeyPackages, it.notAddedUsers) + assertEquals(usersWithKeyPackages, it.successfullyAddedUsers) + } + + verify(arrangement.mlsClient) + .function(arrangement.mlsClient::createConversation) + .with(eq(Arrangement.RAW_GROUP_ID), eq(listOf(Arrangement.CRYPTO_MLS_PUBLIC_KEY))) + .wasInvoked(once) + + verify(arrangement.mlsClient) + .suspendFunction(arrangement.mlsClient::addMember) + .with(eq(Arrangement.RAW_GROUP_ID), matching { it.size == usersWithKeyPackages.size }) + .wasInvoked(exactly = once) + + verify(arrangement.mlsMessageApi) + .suspendFunction(arrangement.mlsMessageApi::sendCommitBundle) + .with(anyInstanceOf(MLSMessageApi.CommitBundle::class)) + .wasInvoked(exactly = once) + + verify(arrangement.mlsClient) + .function(arrangement.mlsClient::commitAccepted) + .with(eq(Arrangement.RAW_GROUP_ID)) + .wasInvoked(exactly = once) + + verify(arrangement.mlsClient) + .function(arrangement.mlsClient::wipeConversation) + .with(eq(Arrangement.RAW_GROUP_ID)) + .wasNotInvoked() + } + @Test fun givenNewCrlDistributionPoints_whenEstablishingMLSGroup_thenCheckRevocationList() = runTest { val (arrangement, mlsConversationRepository) = Arrangement() diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/JoinExistingMLSConversationUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/JoinExistingMLSConversationUseCaseTest.kt index 0201c8f8a28..113ed7757a6 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/JoinExistingMLSConversationUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/JoinExistingMLSConversationUseCaseTest.kt @@ -25,6 +25,7 @@ import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.conversation.ConversationRepository import com.wire.kalium.logic.data.conversation.JoinExistingMLSConversationUseCaseImpl import com.wire.kalium.logic.data.conversation.MLSConversationRepository +import com.wire.kalium.logic.data.conversation.mls.MLSAdditionResult import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.GroupID import com.wire.kalium.logic.data.user.UserId @@ -115,7 +116,6 @@ class JoinExistingMLSConversationUseCaseTest { .withIsMLSSupported(true) .withHasRegisteredMLSClient(true) .withGetConversationsByIdSuccessful(Arrangement.MLS_UNESTABLISHED_GROUP_CONVERSATION) - .withEstablishMLSGroupSuccessful() .arrange() joinExistingMLSConversationsUseCase(Arrangement.MLS_UNESTABLISHED_GROUP_CONVERSATION.id).shouldSucceed() @@ -133,7 +133,7 @@ class JoinExistingMLSConversationUseCaseTest { .withIsMLSSupported(true) .withHasRegisteredMLSClient(true) .withGetConversationsByIdSuccessful(Arrangement.MLS_UNESTABLISHED_SELF_CONVERSATION) - .withEstablishMLSGroupSuccessful() + .withEstablishMLSGroupSuccessful(MLSAdditionResult(emptySet(), emptySet())) .arrange() joinExistingMLSConversationsUseCase(Arrangement.MLS_UNESTABLISHED_SELF_CONVERSATION.id).shouldSucceed() @@ -153,7 +153,7 @@ class JoinExistingMLSConversationUseCaseTest { .withHasRegisteredMLSClient(true) .withGetConversationsByIdSuccessful(Arrangement.MLS_UNESTABLISHED_ONE_ONE_ONE_CONVERSATION) .withGetConversationMembersSuccessful(members) - .withEstablishMLSGroupSuccessful() + .withEstablishMLSGroupSuccessful(MLSAdditionResult(emptySet(), emptySet())) .arrange() joinExistingMLSConversationsUseCase(Arrangement.MLS_UNESTABLISHED_ONE_ONE_ONE_CONVERSATION.id).shouldSucceed() @@ -251,11 +251,11 @@ class JoinExistingMLSConversationUseCaseTest { .then { Either.Right(members) } } - fun withEstablishMLSGroupSuccessful() = apply { + fun withEstablishMLSGroupSuccessful(additionResult: MLSAdditionResult) = apply { given(mlsConversationRepository) .suspendFunction(mlsConversationRepository::establishMLSGroup) .whenInvokedWith(anything(), anything()) - .thenReturn(Either.Right(Unit)) + .thenReturn(Either.Right(additionResult)) } fun withJoinByExternalCommitSuccessful() = apply { diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/mlsmigration/MLSMigratorTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/mlsmigration/MLSMigratorTest.kt index 23294f89b42..b860f7b2b7b 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/mlsmigration/MLSMigratorTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/mlsmigration/MLSMigratorTest.kt @@ -23,6 +23,7 @@ import com.wire.kalium.logic.data.call.CallRepository import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.conversation.ConversationRepository import com.wire.kalium.logic.data.conversation.MLSConversationRepository +import com.wire.kalium.logic.data.conversation.mls.MLSAdditionResult import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.SelfTeamIdProvider import com.wire.kalium.logic.data.message.SystemMessageInserter @@ -65,7 +66,7 @@ class MLSMigratorTest { .withUpdateProtocolReturns() .withFetchConversationSucceeding() .withGetConversationProtocolInfoReturning(Arrangement.MIXED_PROTOCOL_INFO) - .withEstablishGroupSucceeds() + .withEstablishGroupSucceeds(Arrangement.SUCCESSFUL_ADDITION_RESULT) .withGetConversationMembersReturning(Arrangement.MEMBERS) .withAddMembersSucceeds() .withoutAnyEstablishedCall() @@ -99,7 +100,7 @@ class MLSMigratorTest { .withUpdateProtocolReturns() .withFetchConversationSucceeding() .withGetConversationProtocolInfoReturning(Arrangement.MIXED_PROTOCOL_INFO) - .withEstablishGroupSucceeds() + .withEstablishGroupSucceeds(Arrangement.SUCCESSFUL_ADDITION_RESULT) .withGetConversationMembersReturning(Arrangement.MEMBERS) .withAddMembersSucceeds() .withEstablishedCall() @@ -262,11 +263,11 @@ class MLSMigratorTest { .thenReturn(result) } - fun withEstablishGroupSucceeds() = apply { + fun withEstablishGroupSucceeds(additionResult: MLSAdditionResult) = apply { given(mlsConversationRepository) .suspendFunction(mlsConversationRepository::establishMLSGroup) .whenInvokedWith(anything(), anything()) - .thenReturn(Either.Right(Unit)) + .thenReturn(Either.Right(additionResult)) } fun withEstablishGroupFails() = apply { @@ -319,6 +320,7 @@ class MLSMigratorTest { ErrorResponse(409, "", "mls-stale-message") ) val MEMBERS = listOf(TestUser.USER_ID) + val SUCCESSFUL_ADDITION_RESULT = MLSAdditionResult(MEMBERS.toSet(), emptySet()) val MIXED_PROTOCOL_INFO = Conversation.ProtocolInfo.Mixed( TestConversation.GROUP_ID, Conversation.ProtocolInfo.MLSCapable.GroupState.PENDING_JOIN, From 5c76f006cd8562b28863212dc527cd1e61e42e42 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Mon, 11 Mar 2024 19:39:05 +0100 Subject: [PATCH 3/3] chore: update CC to 1.0.0-rc.50 (#2619) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index efba8836d3c..144288ff798 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ pbandk = "0.14.2" turbine = "1.0.0" avs = "9.6.13" jna = "5.14.0" -core-crypto = "1.0.0-rc.49" +core-crypto = "1.0.0-rc.50" core-crypto-multiplatform = "0.6.0-rc.3-multiplatform-pre1" completeKotlin = "1.1.0" desugar-jdk = "2.0.4"