Skip to content

Commit

Permalink
fix(e2ei): fetch MLS verification status UseCase [WPB-8610] (#2705)
Browse files Browse the repository at this point in the history
* Commit with unresolved merge conflicts outside of

* trigger CI

* fix tests after merge

---------

Co-authored-by: boris <[email protected]>
Co-authored-by: Mojtaba Chenani <[email protected]>
  • Loading branch information
3 people authored Apr 26, 2024
1 parent 64127a5 commit 1605a31
Show file tree
Hide file tree
Showing 11 changed files with 453 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ 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.Conversation.ProtocolInfo.MLSCapable.GroupState
import com.wire.kalium.logic.data.conversation.mls.EpochChangesData
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.GroupID
import com.wire.kalium.logic.data.id.IdMapper
Expand Down Expand Up @@ -71,7 +72,6 @@ import com.wire.kalium.persistence.dao.client.ClientDAO
import com.wire.kalium.persistence.dao.conversation.ConversationDAO
import com.wire.kalium.persistence.dao.conversation.ConversationEntity
import com.wire.kalium.persistence.dao.conversation.ConversationMetaDataDAO
import com.wire.kalium.persistence.dao.conversation.EpochChangesDataEntity
import com.wire.kalium.persistence.dao.member.MemberDAO
import com.wire.kalium.persistence.dao.message.MessageDAO
import com.wire.kalium.persistence.dao.message.draft.MessageDraftDAO
Expand Down Expand Up @@ -297,7 +297,7 @@ interface ConversationRepository {

suspend fun observeLegalHoldStatusChangeNotified(conversationId: ConversationId): Flow<Either<StorageFailure, Boolean>>

suspend fun getGroupStatusMembersNamesAndHandles(groupID: GroupID): Either<StorageFailure, EpochChangesDataEntity>
suspend fun getGroupStatusMembersNamesAndHandles(groupID: GroupID): Either<StorageFailure, EpochChangesData>
}

@Suppress("LongParameterList", "TooManyFunctions", "LargeClass")
Expand Down Expand Up @@ -1111,10 +1111,10 @@ internal class ConversationDataSource internal constructor(
.wrapStorageRequest()
.distinctUntilChanged()

override suspend fun getGroupStatusMembersNamesAndHandles(groupID: GroupID): Either<StorageFailure, EpochChangesDataEntity> =
override suspend fun getGroupStatusMembersNamesAndHandles(groupID: GroupID): Either<StorageFailure, EpochChangesData> =
wrapStorageRequest {
conversationDAO.selectGroupStatusMembersNamesAndHandles(groupID.value)
}
}.map { EpochChangesData.fromEntity(it) }

companion object {
const val DEFAULT_MEMBER_ROLE = "wire_member"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Wire
* Copyright (C) 2024 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.data.conversation.mls

import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.conversation.toModel
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.data.id.toModel
import com.wire.kalium.persistence.dao.conversation.EpochChangesDataEntity
import com.wire.kalium.persistence.dao.conversation.NameAndHandleEntity

data class NameAndHandle(
val name: String?,
val handle: String?
) {
companion object {
fun fromEntity(entity: NameAndHandleEntity) = NameAndHandle(entity.name, entity.handle)
}
}

data class EpochChangesData(
val conversationId: QualifiedID,
val mlsVerificationStatus: Conversation.VerificationStatus,
val members: Map<QualifiedID, NameAndHandle>
) {
companion object {
fun fromEntity(entity: EpochChangesDataEntity) = EpochChangesData(
entity.conversationId.toModel(),
entity.mlsVerificationStatus.toModel(),
entity.members.map { (key, value) -> key.toModel() to NameAndHandle.fromEntity(value) }.toMap()
)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ import com.wire.kalium.logic.data.e2ei.CertificateRevocationListRepository
import com.wire.kalium.logic.data.e2ei.CertificateRevocationListRepositoryDataSource
import com.wire.kalium.logic.data.e2ei.E2EIRepository
import com.wire.kalium.logic.data.e2ei.E2EIRepositoryImpl
import com.wire.kalium.logic.data.e2ei.MLSConversationsVerificationStatusesHandler
import com.wire.kalium.logic.data.e2ei.MLSConversationsVerificationStatusesHandlerImpl
import com.wire.kalium.logic.feature.e2ei.usecase.ObserveE2EIConversationsVerificationStatusesUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.ObserveE2EIConversationsVerificationStatusesUseCaseImpl
import com.wire.kalium.logic.data.event.EventDataSource
import com.wire.kalium.logic.data.event.EventRepository
import com.wire.kalium.logic.data.featureConfig.FeatureConfigDataSource
Expand Down Expand Up @@ -214,6 +214,10 @@ import com.wire.kalium.logic.feature.e2ei.ACMECertificatesSyncWorkerImpl
import com.wire.kalium.logic.feature.e2ei.CheckCrlRevocationListUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.CheckRevocationListUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.CheckRevocationListUseCaseImpl
import com.wire.kalium.logic.feature.e2ei.usecase.FetchConversationMLSVerificationStatusUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.FetchConversationMLSVerificationStatusUseCaseImpl
import com.wire.kalium.logic.feature.e2ei.usecase.FetchMLSVerificationStatusUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.FetchMLSVerificationStatusUseCaseImpl
import com.wire.kalium.logic.feature.featureConfig.SyncFeatureConfigsUseCase
import com.wire.kalium.logic.feature.featureConfig.SyncFeatureConfigsUseCaseImpl
import com.wire.kalium.logic.feature.featureConfig.handler.AppLockConfigHandler
Expand Down Expand Up @@ -1955,6 +1959,9 @@ class UserSessionScope internal constructor(
val observeScreenshotCensoringConfig: ObserveScreenshotCensoringConfigUseCase
get() = ObserveScreenshotCensoringConfigUseCaseImpl(userConfigRepository = userConfigRepository)

val fetchConversationMLSVerificationStatus: FetchConversationMLSVerificationStatusUseCase
get() = FetchConversationMLSVerificationStatusUseCaseImpl(conversationRepository, fetchMLSVerificationStatusUseCase)

val kaliumFileSystem: KaliumFileSystem by lazy {
// Create the cache and asset storage directories
KaliumFileSystemImpl(dataStoragePaths).also {
Expand All @@ -1981,19 +1988,26 @@ class UserSessionScope internal constructor(

private val epochChangesObserver by lazy { EpochChangesObserverImpl(epochsFlow) }

private val mlsConversationsVerificationStatusesHandler: MLSConversationsVerificationStatusesHandler by lazy {
MLSConversationsVerificationStatusesHandlerImpl(
private val fetchMLSVerificationStatusUseCase: FetchMLSVerificationStatusUseCase by lazy {
FetchMLSVerificationStatusUseCaseImpl(
conversationRepository,
persistMessage,
mlsClientProvider,
mlsConversationRepository,
epochChangesObserver,
userId,
userRepository,
userScopedLogger,
)
}

private val observeE2EIConversationsVerificationStatuses: ObserveE2EIConversationsVerificationStatusesUseCase by lazy {
ObserveE2EIConversationsVerificationStatusesUseCaseImpl(
fetchMLSVerificationStatusUseCase,
epochChangesObserver,
userScopedLogger,
)
}

private val typingIndicatorSyncManager: TypingIndicatorSyncManager =
TypingIndicatorSyncManager(
typingIndicatorIncomingRepository = lazy { conversations.typingIndicatorIncomingRepository },
Expand Down Expand Up @@ -2034,7 +2048,11 @@ class UserSessionScope internal constructor(
}

launch {
mlsConversationsVerificationStatusesHandler.invoke()
avsSyncStateReporter.execute()
}

launch {
observeE2EIConversationsVerificationStatuses.invoke()
}

launch {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Wire
* Copyright (C) 2024 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.Conversation
import com.wire.kalium.logic.data.conversation.ConversationRepository
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.functional.onSuccess

/**
* Trigger the checking and updating MLS Conversations Verification status.
*/
interface FetchConversationMLSVerificationStatusUseCase {
suspend operator fun invoke(conversationId: ConversationId)
}

internal class FetchConversationMLSVerificationStatusUseCaseImpl(
private val conversationRepository: ConversationRepository,
private val fetchMLSVerificationStatusUseCase: FetchMLSVerificationStatusUseCase
) : FetchConversationMLSVerificationStatusUseCase {

override suspend fun invoke(conversationId: ConversationId) {
conversationRepository.detailsById(conversationId).onSuccess {
if (it.protocol is Conversation.ProtocolInfo.MLSCapable)
fetchMLSVerificationStatusUseCase(it.protocol.groupId)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* 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.data.e2ei
package com.wire.kalium.logic.feature.e2ei.usecase

import com.benasher44.uuid.uuid4
import com.wire.kalium.cryptography.CryptoCertificateStatus
Expand All @@ -26,17 +26,15 @@ import com.wire.kalium.logic.data.client.MLSClientProvider
import com.wire.kalium.logic.data.conversation.Conversation.VerificationStatus
import com.wire.kalium.logic.data.conversation.ConversationRepository
import com.wire.kalium.logic.data.conversation.MLSConversationRepository
import com.wire.kalium.logic.data.conversation.mls.EpochChangesData
import com.wire.kalium.logic.data.conversation.toModel
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.GroupID
import com.wire.kalium.logic.data.id.toDao
import com.wire.kalium.logic.data.id.toModel
import com.wire.kalium.logic.data.message.Message
import com.wire.kalium.logic.data.message.MessageContent
import com.wire.kalium.logic.data.message.PersistMessageUseCase
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.data.user.UserRepository
import com.wire.kalium.logic.data.conversation.EpochChangesObserver
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.flatMap
import com.wire.kalium.logic.functional.getOrElse
Expand All @@ -45,17 +43,16 @@ import com.wire.kalium.logic.functional.map
import com.wire.kalium.logic.functional.onSuccess
import com.wire.kalium.logic.functional.right
import com.wire.kalium.logic.wrapMLSRequest
import com.wire.kalium.persistence.dao.conversation.EpochChangesDataEntity
import com.wire.kalium.util.DateTimeUtil

typealias UserToWireIdentity = Map<UserId, List<WireIdentity>>

/**
* Observes all the MLS Conversations Verification status.
* Check and update MLS Conversations Verification status.
* Notify user (by adding System message in conversation) if needed about changes.
*/
internal interface MLSConversationsVerificationStatusesHandler {
suspend operator fun invoke()
internal interface FetchMLSVerificationStatusUseCase {
suspend operator fun invoke(groupId: GroupID)
}

data class VerificationStatusData(
Expand All @@ -65,62 +62,57 @@ data class VerificationStatusData(
)

@Suppress("LongParameterList")
internal class MLSConversationsVerificationStatusesHandlerImpl(
internal class FetchMLSVerificationStatusUseCaseImpl(
private val conversationRepository: ConversationRepository,
private val persistMessage: PersistMessageUseCase,
private val mlsClientProvider: MLSClientProvider,
private val mlsConversationRepository: MLSConversationRepository,
private val epochChangesObserver: EpochChangesObserver,
private val selfUserId: UserId,
private val userRepository: UserRepository,
kaliumLogger: KaliumLogger
) : MLSConversationsVerificationStatusesHandler {

private val logger = kaliumLogger.withTextTag("MLSConversationsVerificationStatusesHandler")

override suspend fun invoke() {
logger.d("Starting to monitor")
epochChangesObserver.observe()
.collect { groupId ->

mlsClientProvider.getMLSClient()
.flatMap { mlsClient ->
wrapMLSRequest { mlsClient.isGroupVerified(groupId.value) }.map {
it.toModel()
}
}.flatMap { ccGroupStatus ->
if (ccGroupStatus == VerificationStatus.VERIFIED) {
verifyUsersStatus(groupId)
} else {
conversationRepository.getConversationDetailsByMLSGroupId(groupId).map {
VerificationStatusData(
conversationId = it.conversation.id,
currentPersistedStatus = it.conversation.mlsVerificationStatus,
newStatus =
ccGroupStatus
)
}
}
}.onSuccess {
updateStatusAndNotifyUserIfNeeded(it)
) : FetchMLSVerificationStatusUseCase {

private val logger = kaliumLogger.withTextTag("FetchMLSVerificationStatusUseCaseImpl")

override suspend fun invoke(groupId: GroupID) {
mlsClientProvider.getMLSClient()
.flatMap { mlsClient ->
wrapMLSRequest { mlsClient.isGroupVerified(groupId.value) }.map {
it.toModel()
}
}.flatMap { ccGroupStatus ->
if (ccGroupStatus == VerificationStatus.VERIFIED) {
verifyUsersStatus(groupId)
} else {
conversationRepository.getConversationDetailsByMLSGroupId(groupId).map {
VerificationStatusData(
conversationId = it.conversation.id,
currentPersistedStatus = it.conversation.mlsVerificationStatus,
newStatus =
ccGroupStatus
)
}
}
}.onSuccess {
updateStatusAndNotifyUserIfNeeded(it)
}
}

private suspend fun verifyUsersStatus(groupId: GroupID): Either<CoreFailure, VerificationStatusData> =
conversationRepository.getGroupStatusMembersNamesAndHandles(groupId)
.flatMap { epochChangesData ->
mlsConversationRepository.getMembersIdentities(
epochChangesData.conversationId.toModel(),
epochChangesData.members.keys.map { it.toModel() })
epochChangesData.conversationId,
epochChangesData.members.keys.toList()
)
.flatMap { ccIdentities ->
updateKnownUsersIfNeeded(epochChangesData, ccIdentities, groupId)
}
}.map { (dbData, ccIdentity) ->
var newStatus: VerificationStatus = VerificationStatus.VERIFIED
// check that all identities are valid and name and handle are matching
for ((userId, wireIdentity) in ccIdentity) {
val persistedMemberInfo = dbData.members[userId.toDao()]
val persistedMemberInfo = dbData.members[userId]
val isUserVerified = wireIdentity.firstOrNull {
it.status != CryptoCertificateStatus.VALID ||
it.certificate == null ||
Expand All @@ -133,21 +125,21 @@ internal class MLSConversationsVerificationStatusesHandlerImpl(
}
}
VerificationStatusData(
conversationId = dbData.conversationId.toModel(),
currentPersistedStatus = dbData.mlsVerificationStatus.toModel(),
conversationId = dbData.conversationId,
currentPersistedStatus = dbData.mlsVerificationStatus,
newStatus = newStatus
)
}

private suspend fun updateKnownUsersIfNeeded(
epochChangesData: EpochChangesDataEntity,
epochChangesData: EpochChangesData,
ccIdentities: UserToWireIdentity,
groupId: GroupID
): Either<CoreFailure, Pair<EpochChangesDataEntity, UserToWireIdentity>> {
): Either<CoreFailure, Pair<EpochChangesData, UserToWireIdentity>> {
var dbData = epochChangesData

val missingUsers = missingUsers(
usersFromDB = epochChangesData.members.keys.map { it.toModel() }.toSet(),
usersFromDB = epochChangesData.members.keys.map { it }.toSet(),
usersFromCC = ccIdentities.keys
)

Expand Down
Loading

0 comments on commit 1605a31

Please sign in to comment.