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 d8185faf11c..da27400302b 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 @@ -154,6 +154,7 @@ internal class ConversationGroupRepositoryImpl( val conversationEntity = conversationMapper.fromApiModelToDaoModel( conversationResponse, mlsGroupState = ConversationEntity.GroupState.PENDING_CREATION, selfTeamId ) + val mlsPublicKeys = conversationMapper.fromApiModel(conversationResponse.publicKeys) val protocol = protocolInfoMapper.fromEntity(conversationEntity.protocolInfo) return wrapStorageRequest { @@ -166,7 +167,8 @@ internal class ConversationGroupRepositoryImpl( is Conversation.ProtocolInfo.MLSCapable -> mlsConversationRepository.establishMLSGroup( groupID = protocol.groupId, members = usersList + selfUserId, - allowSkippingUsersWithoutKeyPackages = true + publicKeys = mlsPublicKeys, + allowSkippingUsersWithoutKeyPackages = true, ).map { it.notAddedUsers } } }.flatMap { protocolSpecificAdditionFailures -> diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt index c80556243bd..6a0a1c118f9 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt @@ -28,6 +28,7 @@ import com.wire.kalium.logic.data.id.toApi import com.wire.kalium.logic.data.id.toDao import com.wire.kalium.logic.data.id.toModel import com.wire.kalium.logic.data.message.MessagePreview +import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeys import com.wire.kalium.logic.data.user.AvailabilityStatusMapper import com.wire.kalium.logic.data.user.BotService import com.wire.kalium.logic.data.user.Connection @@ -41,6 +42,7 @@ import com.wire.kalium.network.api.authenticated.conversation.ConvTeamInfo import com.wire.kalium.network.api.authenticated.conversation.ConversationResponse import com.wire.kalium.network.api.authenticated.conversation.CreateConversationRequest import com.wire.kalium.network.api.authenticated.conversation.ReceiptMode +import com.wire.kalium.network.api.authenticated.serverpublickey.MLSPublicKeysDTO import com.wire.kalium.network.api.model.ConversationAccessDTO import com.wire.kalium.network.api.model.ConversationAccessRoleDTO import com.wire.kalium.persistence.dao.conversation.ConversationEntity @@ -59,6 +61,8 @@ import kotlin.time.toDuration interface ConversationMapper { fun fromApiModelToDaoModel(apiModel: ConversationResponse, mlsGroupState: GroupState?, selfUserTeamId: TeamId?): ConversationEntity + fun fromApiModel(mlsPublicKeysDTO: MLSPublicKeysDTO?): MLSPublicKeys? + fun fromDaoModel(daoModel: ConversationViewEntity): Conversation fun fromDaoModel(daoModel: ConversationEntity): Conversation fun fromDaoModelToDetails( daoModel: ConversationViewEntity, @@ -164,6 +168,41 @@ internal class ConversationMapperImpl( ) } + override fun fromApiModel(mlsPublicKeysDTO: MLSPublicKeysDTO?) = mlsPublicKeysDTO?.let { + MLSPublicKeys( + removal = mlsPublicKeysDTO.removal + ) + } + + override fun fromDaoModel(daoModel: ConversationViewEntity): Conversation = with(daoModel) { + val lastReadDateEntity = if (type == ConversationEntity.Type.CONNECTION_PENDING) Instant.UNIX_FIRST_DATE + else lastReadDate + + Conversation( + id = id.toModel(), + name = name, + type = type.fromDaoModelToType(), + teamId = teamId?.let { TeamId(it) }, + protocol = protocolInfoMapper.fromEntity(protocolInfo), + mutedStatus = conversationStatusMapper.fromMutedStatusDaoModel(mutedStatus), + removedBy = removedBy?.let { conversationStatusMapper.fromRemovedByToLogicModel(it) }, + lastNotificationDate = lastNotificationDate, + lastModifiedDate = lastModifiedDate, + lastReadDate = lastReadDateEntity, + access = accessList.map { it.toDAO() }, + accessRole = accessRoleList.map { it.toDAO() }, + creatorId = creatorId, + receiptMode = receiptModeMapper.fromEntityToModel(receiptMode), + messageTimer = messageTimer?.toDuration(DurationUnit.MILLISECONDS), + userMessageTimer = userMessageTimer?.toDuration(DurationUnit.MILLISECONDS), + archived = archived, + archivedDateTime = archivedDateTime, + mlsVerificationStatus = verificationStatusFromEntity(mlsVerificationStatus), + proteusVerificationStatus = verificationStatusFromEntity(proteusVerificationStatus), + legalHoldStatus = legalHoldStatusFromEntity(legalHoldStatus) + ) + } + override fun fromDaoModel(daoModel: ConversationEntity): Conversation = with(daoModel) { val lastReadDateEntity = if (type == ConversationEntity.Type.CONNECTION_PENDING) Instant.UNIX_FIRST_DATE else lastReadDate 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 058a4736ce7..d3e2e458fbc 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 @@ -45,7 +45,9 @@ import com.wire.kalium.logic.data.id.toModel import com.wire.kalium.logic.data.keypackage.KeyPackageLimitsProvider import com.wire.kalium.logic.data.keypackage.KeyPackageRepository import com.wire.kalium.logic.data.mls.CipherSuite +import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeys import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeysRepository +import com.wire.kalium.logic.data.mlspublickeys.getRemovalKey import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.di.MapperProvider import com.wire.kalium.logic.data.e2ei.RevocationListChecker @@ -123,6 +125,7 @@ interface MLSConversationRepository { suspend fun establishMLSGroup( groupID: GroupID, members: List, + publicKeys: MLSPublicKeys? = null, allowSkippingUsersWithoutKeyPackages: Boolean = false ): Either @@ -554,16 +557,18 @@ internal class MLSConversationDataSource( override suspend fun establishMLSGroup( groupID: GroupID, members: List, - allowSkippingUsersWithoutKeyPackages: Boolean, + publicKeys: MLSPublicKeys?, + allowSkippingUsersWithoutKeyPackages: Boolean ): Either = withContext(serialDispatcher) { - mlsClientProvider.getMLSClient().flatMap { - mlsPublicKeysRepository.getKeyForCipherSuite( - CipherSuite.fromTag(it.getDefaultCipherSuite()) - ).flatMap { key -> + mlsClientProvider.getMLSClient().flatMap { mlsClient -> + val cipherSuite = CipherSuite.fromTag(mlsClient.getDefaultCipherSuite()) + val keys = publicKeys?.getRemovalKey(cipherSuite) ?: mlsPublicKeysRepository.getKeyForCipherSuite(cipherSuite) + + keys.flatMap { externalSenders -> establishMLSGroup( groupID = groupID, members = members, - externalSenders = key, + externalSenders = externalSenders, allowPartialMemberList = allowSkippingUsersWithoutKeyPackages ) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKeysRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKeysRepository.kt index 48709255f9b..180c506a838 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKeysRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/mlspublickeys/MLSPublicKeysRepository.kt @@ -34,6 +34,15 @@ data class MLSPublicKeys( val removal: Map? ) +fun MLSPublicKeys.getRemovalKey(cipherSuite: CipherSuite): Either { + val mlsPublicKeysMapper: MLSPublicKeysMapper = MapperProvider.mlsPublicKeyMapper() + val keySignature = mlsPublicKeysMapper.fromCipherSuite(cipherSuite) + val key = this.removal?.let { removalKeys -> + removalKeys[keySignature.value] + } ?: return Either.Left(MLSFailure.Generic(IllegalStateException("No key found for cipher suite $cipherSuite"))) + return key.decodeBase64Bytes().right() +} + interface MLSPublicKeysRepository { suspend fun fetchKeys(): Either suspend fun getKeys(): Either @@ -42,7 +51,6 @@ interface MLSPublicKeysRepository { class MLSPublicKeysRepositoryImpl( private val mlsPublicKeyApi: MLSPublicKeyApi, - private val mlsPublicKeysMapper: MLSPublicKeysMapper = MapperProvider.mlsPublicKeyMapper() ) : MLSPublicKeysRepository { // TODO: make it thread safe @@ -60,14 +68,8 @@ class MLSPublicKeysRepositoryImpl( } override suspend fun getKeyForCipherSuite(cipherSuite: CipherSuite): Either { - return getKeys().flatMap { serverPublicKeys -> - val keySignature = mlsPublicKeysMapper.fromCipherSuite(cipherSuite) - val key = serverPublicKeys.removal?.let { removalKeys -> - removalKeys[keySignature.value] - } ?: return Either.Left(MLSFailure.Generic(IllegalStateException("No key found for cipher suite $cipherSuite"))) - key.decodeBase64Bytes().right() + serverPublicKeys.getRemovalKey(cipherSuite) } } - } 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 7bc8eba600a..8a5a552ff6e 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 @@ -422,7 +422,7 @@ class ConversationGroupRepositoryTest { }.wasInvoked(once) coVerify { - mlsConversationRepository.establishMLSGroup(any(), any(), eq(true)) + mlsConversationRepository.establishMLSGroup(any(), any(), any(), eq(true)) }.wasInvoked(once) coVerify { @@ -465,7 +465,7 @@ class ConversationGroupRepositoryTest { }.wasInvoked(once) coVerify { - mlsConversationRepository.establishMLSGroup(any(), any(), eq(true)) + mlsConversationRepository.establishMLSGroup(any(), any(), any(), eq(true)) }.wasInvoked(once) coVerify { @@ -1723,11 +1723,10 @@ class ConversationGroupRepositoryTest { legalHoldHandler ) - suspend fun withMlsConversationEstablished(additionResult: MLSAdditionResult): Arrangement { + suspend fun withMlsConversationEstablished(additionResult: MLSAdditionResult): Arrangement = apply { coEvery { - mlsConversationRepository.establishMLSGroup(any(), any(), any()) + mlsConversationRepository.establishMLSGroup(any(), any(), any(), any()) }.returns(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 37e358d4d5a..52ab07b042b 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 @@ -40,6 +40,7 @@ import com.wire.kalium.logic.data.conversation.MLSConversationRepositoryTest.Arr 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.MLS_PUBLIC_KEY 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 @@ -98,26 +99,30 @@ import io.mockative.matches import io.mockative.mock import io.mockative.once import io.mockative.twice +import io.mockative.verify import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import kotlinx.coroutines.yield import kotlinx.datetime.Instant +import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs class MLSConversationRepositoryTest { + @BeforeTest + @Test - fun givenCommitMessage_whenDecryptingMessage_thenEmitEpochChange() = runTest(TestKaliumDispatcher.default) { + fun givenCommitMessage_whenDecryptingMessage_thenEmitEpochChange() = runTest { val (arrangement, mlsConversationRepository) = Arrangement(testKaliumDispatcher) .withGetMLSClientSuccessful() .withDecryptMLSMessageSuccessful(Arrangement.DECRYPTED_MESSAGE_BUNDLE) .arrange() - val epochChange = async(TestKaliumDispatcher.default) { + val epochChange = async() { arrangement.epochsFlow.first() } yield() @@ -168,7 +173,7 @@ class MLSConversationRepositoryTest { .withSendCommitBundleSuccessful() .arrange() - val result = mlsConversationRepository.establishMLSGroup(Arrangement.GROUP_ID, listOf(TestConversation.USER_1)) + val result = mlsConversationRepository.establishMLSGroup(Arrangement.GROUP_ID, listOf(TestConversation.USER_1), publicKeys = null) result.shouldSucceed() coVerify { @@ -280,6 +285,84 @@ class MLSConversationRepositoryTest { }.wasNotInvoked() } + @Test + fun givenPublicKeysIsNotNull_whenCallingEstablishMLSGroup_ThenGetPublicKeysRepositoryNotCalled() = runTest { + val (arrangement, mlsConversationRepository) = Arrangement(kaliumDispatcher = testKaliumDispatcher) + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) + .withCommitPendingProposalsReturningNothing() + .withClaimKeyPackagesSuccessful() + .withGetMLSClientSuccessful() + .withKeyForCipherSuite() + .withAddMLSMemberSuccessful() + .withSendCommitBundleSuccessful() + .arrange() + + val result = + mlsConversationRepository.establishMLSGroup(Arrangement.GROUP_ID, listOf(TestConversation.USER_1), publicKeys = MLS_PUBLIC_KEY) + result.shouldSucceed() + + coVerify { + arrangement.mlsClient.createConversation( + groupId = eq(Arrangement.RAW_GROUP_ID), + externalSenders = any()) + }.wasInvoked(once) + + coVerify { + arrangement.mlsClient.addMember( + groupId = eq(Arrangement.RAW_GROUP_ID), + membersKeyPackages = any()) + }.wasInvoked(once) + + coVerify { + arrangement.mlsMessageApi.sendCommitBundle(any()) + }.wasInvoked(once) + + coVerify { + arrangement.mlsClient.commitAccepted(eq(Arrangement.RAW_GROUP_ID)) + }.wasInvoked(once) + + coVerify { + arrangement.mlsPublicKeysRepository.getKeyForCipherSuite(any()) + }.wasNotInvoked() + } + + @Test + fun givenPublicKeysIsNull_whenCallingEstablishMLSGroup_ThenGetPublicKeysRepositoryIsCalled() = runTest { + val (arrangement, mlsConversationRepository) = Arrangement(testKaliumDispatcher) + .withGetDefaultCipherSuite(CipherSuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) + .withCommitPendingProposalsReturningNothing() + .withClaimKeyPackagesSuccessful() + .withGetMLSClientSuccessful() + .withKeyForCipherSuite() + .withAddMLSMemberSuccessful() + .withSendCommitBundleSuccessful() + .arrange() + + val result = + mlsConversationRepository.establishMLSGroup(Arrangement.GROUP_ID, listOf(TestConversation.USER_1), publicKeys = null) + result.shouldSucceed() + + coVerify { + arrangement.mlsClient.createConversation(eq(Arrangement.RAW_GROUP_ID), any()) + }.wasInvoked(once) + + coVerify { + arrangement.mlsClient.addMember(eq(Arrangement.RAW_GROUP_ID), any()) + }.wasInvoked(once) + + coVerify { + arrangement.mlsMessageApi.sendCommitBundle(any()) + }.wasInvoked(once) + + coVerify { + arrangement.mlsClient.commitAccepted(eq(Arrangement.RAW_GROUP_ID)) + }.wasInvoked(once) + + coVerify { + arrangement.mlsPublicKeysRepository.getKeyForCipherSuite(any()) + }.wasInvoked(once) + } + @Test fun givenNewCrlDistributionPoints_whenEstablishingMLSGroup_thenCheckRevocationList() = runTest { val (arrangement, mlsConversationRepository) = Arrangement(testKaliumDispatcher) @@ -329,7 +412,7 @@ class MLSConversationRepositoryTest { .withWaitUntilLiveSuccessful() .arrange() - val result = mlsConversationRepository.establishMLSGroup(Arrangement.GROUP_ID, listOf(TestConversation.USER_1)) + val result = mlsConversationRepository.establishMLSGroup(Arrangement.GROUP_ID, listOf(TestConversation.USER_1), publicKeys = null) result.shouldSucceed() coVerify { @@ -357,7 +440,7 @@ class MLSConversationRepositoryTest { .withSendCommitBundleFailing(Arrangement.MLS_STALE_MESSAGE_ERROR) .arrange() - val result = mlsConversationRepository.establishMLSGroup(Arrangement.GROUP_ID, listOf(TestConversation.USER_1)) + val result = mlsConversationRepository.establishMLSGroup(Arrangement.GROUP_ID, listOf(TestConversation.USER_1), publicKeys = null) result.shouldFail() coVerify { @@ -385,7 +468,7 @@ class MLSConversationRepositoryTest { .withSendCommitBundleSuccessful() .arrange() - val result = mlsConversationRepository.establishMLSGroup(Arrangement.GROUP_ID, listOf(TestConversation.USER_1)) + val result = mlsConversationRepository.establishMLSGroup(Arrangement.GROUP_ID, listOf(TestConversation.USER_1), publicKeys = null) result.shouldSucceed() coVerify { @@ -410,7 +493,7 @@ class MLSConversationRepositoryTest { .withSendCommitBundleSuccessful() .arrange() - val result = mlsConversationRepository.establishMLSGroup(Arrangement.GROUP_ID, emptyList()) + val result = mlsConversationRepository.establishMLSGroup(Arrangement.GROUP_ID, emptyList(), publicKeys = null) result.shouldSucceed() coVerify { 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 cee14f64eea..59264218319 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 @@ -131,7 +131,12 @@ class JoinExistingMLSConversationUseCaseTest { joinExistingMLSConversationsUseCase(Arrangement.MLS_UNESTABLISHED_GROUP_CONVERSATION.id).shouldSucceed() coVerify { - arrangement.mlsConversationRepository.establishMLSGroup(eq(Arrangement.GROUP_ID3), eq(emptyList()), any()) + arrangement.mlsConversationRepository.establishMLSGroup( + groupID = Arrangement.GROUP_ID3, + members = emptyList(), + publicKeys = null, + allowSkippingUsersWithoutKeyPackages = false + ) }.wasNotInvoked() } @@ -148,7 +153,12 @@ class JoinExistingMLSConversationUseCaseTest { joinExistingMLSConversationsUseCase(Arrangement.MLS_UNESTABLISHED_SELF_CONVERSATION.id).shouldSucceed() coVerify { - arrangement.mlsConversationRepository.establishMLSGroup(eq(Arrangement.GROUP_ID_SELF), eq(emptyList()), any()) + arrangement.mlsConversationRepository.establishMLSGroup( + groupID = Arrangement.GROUP_ID_SELF, + members = emptyList(), + publicKeys = null, + allowSkippingUsersWithoutKeyPackages = false + ) }.wasInvoked(once) } @@ -167,7 +177,12 @@ class JoinExistingMLSConversationUseCaseTest { joinExistingMLSConversationsUseCase(Arrangement.MLS_UNESTABLISHED_ONE_ONE_ONE_CONVERSATION.id).shouldSucceed() coVerify { - arrangement.mlsConversationRepository.establishMLSGroup(eq(Arrangement.GROUP_ID_ONE_ON_ONE), eq(members), any()) + arrangement.mlsConversationRepository.establishMLSGroup( + groupID = Arrangement.GROUP_ID_ONE_ON_ONE, + members = members, + publicKeys = null, + allowSkippingUsersWithoutKeyPackages = false + ) }.wasInvoked(once) } @@ -256,7 +271,7 @@ class JoinExistingMLSConversationUseCaseTest { suspend fun withEstablishMLSGroupSuccessful(additionResult: MLSAdditionResult) = apply { coEvery { - mlsConversationRepository.establishMLSGroup(any(), any(), any()) + mlsConversationRepository.establishMLSGroup(any(), any(), any(), any()) }.returns(Either.Right(additionResult)) } 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 7e6e84139a4..46ef572af21 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 @@ -27,6 +27,7 @@ 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.id.TeamId import com.wire.kalium.logic.data.message.SystemMessageInserter import com.wire.kalium.logic.data.mls.CipherSuite import com.wire.kalium.logic.data.user.UserId @@ -78,18 +79,23 @@ class MLSMigratorTest { migrator.migrateProteusConversations() coVerify { - arrangement.conversationRepository.updateProtocolRemotely(eq(conversation.id), eq(Conversation.Protocol.MIXED)) + arrangement.conversationRepository.updateProtocolRemotely(conversation.id, Conversation.Protocol.MIXED) }.wasInvoked(once) coVerify { - arrangement.mlsConversationRepository.establishMLSGroup(eq(Arrangement.MIXED_PROTOCOL_INFO.groupId), eq(emptyList()), any()) + arrangement.mlsConversationRepository.establishMLSGroup( + groupID = Arrangement.MIXED_PROTOCOL_INFO.groupId, + members = emptyList(), + publicKeys = null, + false + ) } coVerify { arrangement.mlsConversationRepository.addMemberToMLSGroup( - eq(Arrangement.MIXED_PROTOCOL_INFO.groupId), - eq(Arrangement.MEMBERS), - eq(CIPHER_SUITE) + Arrangement.MIXED_PROTOCOL_INFO.groupId, + Arrangement.MEMBERS, + CIPHER_SUITE ) } } @@ -119,7 +125,12 @@ class MLSMigratorTest { }.wasInvoked(once) coVerify { - arrangement.mlsConversationRepository.establishMLSGroup(eq(Arrangement.MIXED_PROTOCOL_INFO.groupId), eq(emptyList()), any()) + arrangement.mlsConversationRepository.establishMLSGroup( + groupID = Arrangement.MIXED_PROTOCOL_INFO.groupId, + members = emptyList(), + publicKeys = null, + allowSkippingUsersWithoutKeyPackages = false + ) } coVerify { @@ -232,7 +243,11 @@ class MLSMigratorTest { suspend fun withGetProteusTeamConversationsReturning(conversationsIds: List) = apply { coEvery { - conversationRepository.getConversationIds(eq(Conversation.Type.GROUP), eq(Conversation.Protocol.PROTEUS), any()) + conversationRepository.getConversationIds( + Conversation.Type.GROUP, + Conversation.Protocol.PROTEUS, + TeamId(value = "Some-team") + ) }.returns(Either.Right(conversationsIds)) } @@ -268,13 +283,13 @@ class MLSMigratorTest { suspend fun withEstablishGroupSucceeds(additionResult: MLSAdditionResult) = apply { coEvery { - mlsConversationRepository.establishMLSGroup(any(), any(), any()) + mlsConversationRepository.establishMLSGroup(any(), any(), any(), any()) }.returns(Either.Right(additionResult)) } suspend fun withEstablishGroupFails() = apply { coEvery { - mlsConversationRepository.establishMLSGroup(any(), any(), any()) + mlsConversationRepository.establishMLSGroup(any(), any(), any(), any()) }.returns(Either.Left(NetworkFailure.ServerMiscommunication(MLS_STALE_MESSAGE_ERROR))) } diff --git a/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/authenticated/conversation/ConversationResponse.kt b/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/authenticated/conversation/ConversationResponse.kt index f296b9614c4..bebec817438 100644 --- a/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/authenticated/conversation/ConversationResponse.kt +++ b/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/authenticated/conversation/ConversationResponse.kt @@ -18,6 +18,7 @@ package com.wire.kalium.network.api.authenticated.conversation +import com.wire.kalium.network.api.authenticated.serverpublickey.MLSPublicKeysDTO import com.wire.kalium.network.api.model.ConversationAccessDTO import com.wire.kalium.network.api.model.ConversationAccessRoleDTO import com.wire.kalium.network.api.model.ConversationId @@ -86,7 +87,10 @@ data class ConversationResponse( val accessRole: Set?, @SerialName("receipt_mode") - val receiptMode: ReceiptMode + val receiptMode: ReceiptMode, + + @SerialName("public_keys") + val publicKeys: MLSPublicKeysDTO? = null ) { @Suppress("MagicNumber") @@ -155,6 +159,14 @@ data class ConversationResponseV3( val receiptMode: ReceiptMode, ) +@Serializable +data class ConversationResponseV6( + @SerialName("conversation") + val conversation: ConversationResponseV3, + @SerialName("public_keys") + val publicKeys: MLSPublicKeysDTO +) + @Serializable data class ConversationMembersResponse( @SerialName("self") diff --git a/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/model/ApiModelMapper.kt b/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/model/ApiModelMapper.kt index 330f113e34d..46e7790bf67 100644 --- a/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/model/ApiModelMapper.kt +++ b/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/model/ApiModelMapper.kt @@ -20,6 +20,7 @@ package com.wire.kalium.network.api.model import com.wire.kalium.network.api.authenticated.conversation.ConversationResponse import com.wire.kalium.network.api.authenticated.conversation.ConversationResponseV3 +import com.wire.kalium.network.api.authenticated.conversation.ConversationResponseV6 import com.wire.kalium.network.api.authenticated.conversation.CreateConversationRequest import com.wire.kalium.network.api.authenticated.conversation.CreateConversationRequestV3 import com.wire.kalium.network.api.authenticated.conversation.UpdateConversationAccessRequest @@ -33,6 +34,7 @@ interface ApiModelMapper { fun toApiV3(request: CreateConversationRequest): CreateConversationRequestV3 fun toApiV3(request: UpdateConversationAccessRequest): UpdateConversationAccessRequestV3 fun fromApiV3(response: ConversationResponseV3): ConversationResponse + fun fromApiV6(response: ConversationResponseV6): ConversationResponse } class ApiModelMapperImpl : ApiModelMapper { @@ -76,4 +78,23 @@ class ApiModelMapperImpl : ApiModelMapper { response.receiptMode ) + override fun fromApiV6(response: ConversationResponseV6): ConversationResponse = + ConversationResponse( + creator = response.conversation.creator, + members = response.conversation.members, + name = response.conversation.name, + id = response.conversation.id, + groupId = response.conversation.groupId, + epoch = response.conversation.epoch, + type = response.conversation.type, + messageTimer = response.conversation.messageTimer, + teamId = response.conversation.teamId, + protocol = response.conversation.protocol, + lastEventTime = response.conversation.lastEventTime, + mlsCipherSuiteTag = response.conversation.mlsCipherSuiteTag, + access = response.conversation.access, + accessRole = response.conversation.accessRole, + receiptMode = response.conversation.receiptMode, + publicKeys = response.publicKeys + ) } diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v6/authenticated/ConversationApiV6.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v6/authenticated/ConversationApiV6.kt index bcdaa50c0a4..6a1cd4e8871 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v6/authenticated/ConversationApiV6.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v6/authenticated/ConversationApiV6.kt @@ -19,8 +19,29 @@ package com.wire.kalium.network.api.v6.authenticated import com.wire.kalium.network.AuthenticatedNetworkClient +import com.wire.kalium.network.api.authenticated.conversation.ConversationResponse +import com.wire.kalium.network.api.authenticated.conversation.ConversationResponseV6 +import com.wire.kalium.network.api.authenticated.conversation.CreateConversationRequest +import com.wire.kalium.network.api.model.ApiModelMapper +import com.wire.kalium.network.api.model.ApiModelMapperImpl import com.wire.kalium.network.api.v5.authenticated.ConversationApiV5 +import com.wire.kalium.network.utils.NetworkResponse +import com.wire.kalium.network.utils.mapSuccess +import com.wire.kalium.network.utils.wrapKaliumResponse +import io.ktor.client.request.post +import io.ktor.client.request.setBody internal open class ConversationApiV6 internal constructor( authenticatedNetworkClient: AuthenticatedNetworkClient, -) : ConversationApiV5(authenticatedNetworkClient) + private val apiModelMapper: ApiModelMapper = ApiModelMapperImpl() +) : ConversationApiV5(authenticatedNetworkClient) { + override suspend fun createOne2OneConversation( + createConversationRequest: CreateConversationRequest + ): NetworkResponse = wrapKaliumResponse { + httpClient.post("$PATH_CONVERSATIONS/$PATH_ONE_2_ONE") { + setBody(apiModelMapper.toApiV3(createConversationRequest)) + } + }.mapSuccess { + apiModelMapper.fromApiV6(it) + } +}