Skip to content

Commit

Permalink
feat: Update conversation Proteus verification status
Browse files Browse the repository at this point in the history
  • Loading branch information
borichellow committed Oct 19, 2023
1 parent 675404c commit 551cbd8
Show file tree
Hide file tree
Showing 37 changed files with 555 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ internal class ConnectionDataSource(
userMessageTimer = null,
archived = false,
archivedInstant = null,
verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED
mlsVerificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED,
proteusVerificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import com.wire.kalium.logic.data.user.OtherUser
import com.wire.kalium.logic.data.user.User
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.data.user.type.UserType
import com.wire.kalium.persistence.dao.QualifiedIDEntity
import com.wire.kalium.persistence.dao.conversation.ConversationEntity
import com.wire.kalium.util.serialization.toJsonElement
import com.wire.kalium.util.time.UNIX_FIRST_DATE
import kotlinx.datetime.Instant
Expand Down Expand Up @@ -71,7 +73,8 @@ data class Conversation(
val userMessageTimer: Duration?,
val archived: Boolean,
val archivedDateTime: Instant?,
val verificationStatus: VerificationStatus
val mlsVerificationStatus: VerificationStatus,
val proteusVerificationStatus: VerificationStatus
) {

companion object {
Expand Down Expand Up @@ -246,6 +249,11 @@ data class Conversation(
)
}

data class ProteusVerificationData(
val conversationId: ConversationId,
val currentVerificationStatus: VerificationStatus,
val isActuallyVerified: Boolean
)
}

sealed class ConversationDetails(open val conversation: Conversation) {
Expand Down Expand Up @@ -304,7 +312,8 @@ sealed class ConversationDetails(open val conversation: Conversation) {
userMessageTimer = null,
archived = false,
archivedDateTime = null,
verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED
mlsVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED,
proteusVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ interface ConversationMapper {
fun fromFailedGroupConversationToEntity(conversationId: NetworkQualifiedId): ConversationEntity
fun verificationStatusToEntity(verificationStatus: Conversation.VerificationStatus): ConversationEntity.VerificationStatus
fun verificationStatusFromEntity(verificationStatus: ConversationEntity.VerificationStatus): Conversation.VerificationStatus
fun fromDaoModelToProteusVerificationData(daoModel: ConversationEntity.ProteusVerificationData): Conversation.ProteusVerificationData
}

@Suppress("TooManyFunctions", "LongParameterList")
Expand Down Expand Up @@ -122,7 +123,8 @@ internal class ConversationMapperImpl(
hasIncompleteMetadata = false,
archived = apiModel.members.self.otrArchived ?: false,
archivedInstant = apiModel.members.self.otrArchivedRef?.toInstant(),
verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED
mlsVerificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED,
proteusVerificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED
)

override fun fromApiModelToDaoModel(apiModel: ConvProtocol): Protocol = when (apiModel) {
Expand Down Expand Up @@ -165,7 +167,8 @@ internal class ConversationMapperImpl(
userMessageTimer = userMessageTimer?.toDuration(DurationUnit.MILLISECONDS),
archived = archived,
archivedDateTime = archivedDateTime,
verificationStatus = verificationStatusFromEntity(verificationStatus)
mlsVerificationStatus = verificationStatusFromEntity(mlsVerificationStatus),
proteusVerificationStatus = verificationStatusFromEntity(proteusVerificationStatus)
)
}

Expand All @@ -191,7 +194,8 @@ internal class ConversationMapperImpl(
userMessageTimer = userMessageTimer?.toDuration(DurationUnit.MILLISECONDS),
archived = archived,
archivedDateTime = archivedInstant,
verificationStatus = verificationStatusFromEntity(verificationStatus)
mlsVerificationStatus = verificationStatusFromEntity(mlsVerificationStatus),
proteusVerificationStatus = verificationStatusFromEntity(proteusVerificationStatus)
)
}

Expand Down Expand Up @@ -386,7 +390,8 @@ internal class ConversationMapperImpl(
userMessageTimer = userMessageTimer?.inWholeMilliseconds,
archived = archived,
archivedInstant = archivedDateTime,
verificationStatus = verificationStatusToEntity(verificationStatus)
mlsVerificationStatus = verificationStatusToEntity(mlsVerificationStatus),
proteusVerificationStatus = verificationStatusToEntity(proteusVerificationStatus)
)
}

Expand Down Expand Up @@ -415,7 +420,8 @@ internal class ConversationMapperImpl(
hasIncompleteMetadata = true,
archived = false,
archivedInstant = null,
verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED
mlsVerificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED,
proteusVerificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED
)

private fun ConversationResponse.getProtocolInfo(mlsGroupState: GroupState?): ProtocolInfo {
Expand Down Expand Up @@ -458,6 +464,15 @@ internal class ConversationMapperImpl(

override fun verificationStatusToEntity(verificationStatus: Conversation.VerificationStatus) =
ConversationEntity.VerificationStatus.valueOf(verificationStatus.name)

override fun fromDaoModelToProteusVerificationData(
daoModel: ConversationEntity.ProteusVerificationData,
): Conversation.ProteusVerificationData =
Conversation.ProteusVerificationData(
daoModel.conversationId.toModel(),
verificationStatusFromEntity(daoModel.currentVerificationStatus),
daoModel.isActuallyVerified
)
}

private fun ConversationEntity.Type.fromDaoModelToType(): Conversation.Type = when (this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ interface ConversationRepository {
domain: String
): Either<CoreFailure, OneOnOneMembers>

suspend fun updateVerificationStatus(
suspend fun updateMlsVerificationStatus(
verificationStatus: Conversation.VerificationStatus,
conversationID: ConversationId
): Either<CoreFailure, Unit>
Expand All @@ -242,6 +242,14 @@ interface ConversationRepository {
conversationId: ConversationId,
typingStatus: Conversation.TypingIndicatorMode
): Either<CoreFailure, Unit>

suspend fun getConversationsProteusVerificationDataByClientId(
clientId: ClientId
): Either<StorageFailure, List<Conversation.ProteusVerificationData>>

suspend fun updateProteusVerificationStatuses(
statusesToUpdate: Map<QualifiedID, Conversation.VerificationStatus>
): Either<StorageFailure, Unit>
}

@Suppress("LongParameterList", "TooManyFunctions")
Expand Down Expand Up @@ -853,12 +861,12 @@ internal class ConversationDataSource internal constructor(
.mapValues { it.value.toModel() }
}

override suspend fun updateVerificationStatus(
override suspend fun updateMlsVerificationStatus(
verificationStatus: Conversation.VerificationStatus,
conversationID: ConversationId
): Either<CoreFailure, Unit> =
wrapStorageRequest {
conversationDAO.updateVerificationStatus(
conversationDAO.updateMlsVerificationStatus(
conversationMapper.verificationStatusToEntity(verificationStatus),
conversationID.toDao()
)
Expand All @@ -880,6 +888,22 @@ internal class ConversationDataSource internal constructor(
conversationApi.sendTypingIndicatorNotification(conversationId.toApi(), typingStatus.toStatusDto())
}

override suspend fun updateProteusVerificationStatuses(
statusesToUpdate: Map<QualifiedID, Conversation.VerificationStatus>
): Either<StorageFailure, Unit> =
wrapStorageRequest {
conversationDAO.updateProteusVerificationStatuses(
statusesToUpdate.mapKeys { (conversationId, _) -> conversationId.toDao() }
.mapValues { (_, verificationStatus) -> conversationMapper.verificationStatusToEntity(verificationStatus) }
)
}

override suspend fun getConversationsProteusVerificationDataByClientId(
clientId: ClientId
): Either<StorageFailure, List<Conversation.ProteusVerificationData>> =
wrapStorageRequest { conversationDAO.getConversationsProteusVerificationDataByClientId(clientId.value) }
.map { list -> list.map { conversationMapper.fromDaoModelToProteusVerificationData(it) } }

private suspend fun persistIncompleteConversations(
conversationsFailed: List<NetworkQualifiedId>
): Either<CoreFailure, Unit> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,4 +415,11 @@ sealed interface MessagePreviewContent {

object Unknown : MessagePreviewContent

sealed class VerificationChanged: MessagePreviewContent {
data object VerifiedMls : VerificationChanged()
data object VerifiedProteus : VerificationChanged()
data object DegradedMls : VerificationChanged()
data object DegradedProteus : VerificationChanged()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,10 @@ private fun MessagePreviewEntityContent.toMessageContent(): MessagePreviewConten
is MessagePreviewEntityContent.CryptoSessionReset -> MessagePreviewContent.CryptoSessionReset
MessagePreviewEntityContent.Unknown -> MessagePreviewContent.Unknown
is MessagePreviewEntityContent.Composite -> MessagePreviewContent.WithUser.Composite(username = senderName, messageBody = messageBody)
is MessagePreviewEntityContent.ConversationVerifiedMls -> MessagePreviewContent.VerificationChanged.VerifiedMls
is MessagePreviewEntityContent.ConversationVerifiedProteus -> MessagePreviewContent.VerificationChanged.VerifiedProteus
is MessagePreviewEntityContent.ConversationVerificationDegradedMls -> MessagePreviewContent.VerificationChanged.DegradedMls
is MessagePreviewEntityContent.ConversationVerificationDegradedProteus -> MessagePreviewContent.VerificationChanged.DegradedProteus
}

fun AssetTypeEntity.toModel(): AssetType = when (this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ fun WebConversationContent.toConversation(selfUserId: UserId): Conversation? {
userMessageTimer = null,
archived = archivedState ?: false,
archivedDateTime = conversationArchivedTimestamp,
verificationStatus = Conversation.VerificationStatus.NOT_VERIFIED
mlsVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED,
proteusVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,7 @@ class UserSessionScope internal constructor(
mlsClientProvider,
notificationTokenRepository,
clientRemoteRepository,
conversationRepository,
proteusClientProvider,
globalScope.sessionRepository,
upgradeCurrentSessionUseCase,
Expand All @@ -1296,7 +1297,8 @@ class UserSessionScope internal constructor(
userRepository,
authenticationScope.secondFactorVerificationRepository,
slowSyncRepository,
cachedClientIdClearer
cachedClientIdClearer,
persistMessage
)
val conversations: ConversationScope by lazy {
ConversationScope(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import com.wire.kalium.logic.data.auth.verification.SecondFactorVerificationRepo
import com.wire.kalium.logic.data.client.ClientRepository
import com.wire.kalium.logic.data.client.MLSClientProvider
import com.wire.kalium.logic.data.client.remote.ClientRemoteRepository
import com.wire.kalium.logic.data.conversation.ConversationRepository
import com.wire.kalium.logic.data.keypackage.KeyPackageLimitsProvider
import com.wire.kalium.logic.data.keypackage.KeyPackageRepository
import com.wire.kalium.logic.data.logout.LogoutRepository
import com.wire.kalium.logic.data.message.PersistMessageUseCase
import com.wire.kalium.logic.data.notification.PushTokenRepository
import com.wire.kalium.logic.data.prekey.PreKeyRepository
import com.wire.kalium.logic.data.session.SessionRepository
Expand Down Expand Up @@ -57,6 +59,7 @@ class ClientScope @OptIn(DelicateKaliumApi::class) internal constructor(
private val mlsClientProvider: MLSClientProvider,
private val notificationTokenRepository: NotificationTokenRepository,
private val clientRemoteRepository: ClientRemoteRepository,
private val conversationRepository: ConversationRepository,
private val proteusClientProvider: ProteusClientProvider,
private val sessionRepository: SessionRepository,
private val upgradeCurrentSessionUseCase: UpgradeCurrentSessionUseCase,
Expand All @@ -66,7 +69,8 @@ class ClientScope @OptIn(DelicateKaliumApi::class) internal constructor(
private val userRepository: UserRepository,
private val secondFactorVerificationRepository: SecondFactorVerificationRepository,
private val slowSyncRepository: SlowSyncRepository,
private val cachedClientIdClearer: CachedClientIdClearer
private val cachedClientIdClearer: CachedClientIdClearer,
private val persistMessage: PersistMessageUseCase
) {
@OptIn(DelicateKaliumApi::class)
val register: RegisterClientUseCase
Expand Down Expand Up @@ -142,6 +146,6 @@ class ClientScope @OptIn(DelicateKaliumApi::class) internal constructor(

val remoteClientFingerPrint: ClientFingerprintUseCase get() = ClientFingerprintUseCase(proteusClientProvider, preKeyRepository)
val updateClientVerificationStatus: UpdateClientVerificationStatusUseCase
get() = UpdateClientVerificationStatusUseCase(clientRepository)
get() = UpdateClientVerificationStatusUseCase(clientRepository, conversationRepository, persistMessage, selfUserId)

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,23 @@
*/
package com.wire.kalium.logic.feature.client

import com.benasher44.uuid.uuid4
import com.wire.kalium.logic.StorageFailure
import com.wire.kalium.logic.data.client.ClientRepository
import com.wire.kalium.logic.data.conversation.ClientId
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.data.id.QualifiedID
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.functional.Either
import com.wire.kalium.logic.functional.flatMap
import com.wire.kalium.logic.functional.fold
import com.wire.kalium.logic.functional.map
import com.wire.kalium.util.DateTimeUtil

/**
* Updates the verification status of a client.
Expand All @@ -32,16 +44,72 @@ import com.wire.kalium.logic.functional.fold
* [UpdateClientVerificationStatusUseCase.Result.Failure] if the client could not be updated.
*/
class UpdateClientVerificationStatusUseCase internal constructor(
private val clientRepository: ClientRepository
private val clientRepository: ClientRepository,
private val conversationRepository: ConversationRepository,
private val persistMessage: PersistMessageUseCase,
private val selfUserId: UserId,
) {
suspend operator fun invoke(userId: UserId, clientId: ClientId, verified: Boolean): Result =
clientRepository.updateClientProteusVerificationStatus(userId, clientId, verified).fold(
{ error -> Result.Failure(error) },
{ Result.Success }
)
clientRepository.updateClientProteusVerificationStatus(userId, clientId, verified)
.flatMap { conversationRepository.getConversationsProteusVerificationDataByClientId(clientId) }
.flatMap { updateConversationsStatusIfNeeded(it) }
.map { it.forEach { (conversationId, newStatus) -> notifyUserAboutStatusChanges(conversationId, newStatus) } }
.fold(
{ error -> Result.Failure(error) },
{ Result.Success }
)

sealed interface Result {
object Success : Result
data class Failure(val error: StorageFailure) : Result
}

/**
* Select the conversations that [ClientId] belongs to AND which should update the Proteus verification status
* according to changed conditions:
* - if the client was the last not verified in the conversation and user verified it -> conversation becomes VERIFIED;
* - if the conversation was verified and user un-verify at least 1 client -> conversation becomes DEGRADED;
* And updates the conversations statuses.
*/
private suspend fun updateConversationsStatusIfNeeded(
statusesData: List<Conversation.ProteusVerificationData>
): Either<StorageFailure, MutableMap<QualifiedID, Conversation.VerificationStatus>> {
val mapForUpdatingStatuses = mutableMapOf<QualifiedID, Conversation.VerificationStatus>()

statusesData.forEach {
if (it.isActuallyVerified && it.currentVerificationStatus != Conversation.VerificationStatus.VERIFIED) {
mapForUpdatingStatuses[it.conversationId] = Conversation.VerificationStatus.VERIFIED
} else if (!it.isActuallyVerified && it.currentVerificationStatus == Conversation.VerificationStatus.VERIFIED) {
mapForUpdatingStatuses[it.conversationId] = Conversation.VerificationStatus.DEGRADED
}
}

return if (mapForUpdatingStatuses.isEmpty()) Either.Right(mapForUpdatingStatuses)
else conversationRepository.updateProteusVerificationStatuses(mapForUpdatingStatuses)
.map { mapForUpdatingStatuses }
}

/**
* Add a SystemMessage into a conversation, to inform user when the conversation verification status was changed.
*/
private suspend fun notifyUserAboutStatusChanges(
conversationId: ConversationId,
updatedStatus: Conversation.VerificationStatus
) {
val content = if (updatedStatus == Conversation.VerificationStatus.VERIFIED) MessageContent.ConversationVerifiedProteus
else MessageContent.ConversationDegradedProteus

val conversationDegradedMessage = Message.System(
id = uuid4().toString(),
content = content,
conversationId = conversationId,
date = DateTimeUtil.currentIsoDateTimeString(),
senderUserId = selfUserId,
status = Message.Status.Sent,
visibility = Message.Visibility.VISIBLE,
expirationData = null
)

persistMessage(conversationDegradedMessage)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ internal class MLSConversationsVerificationStatusesHandlerImpl(
}

private suspend fun updateStatusAndNotifyUserIfNeeded(newStatusFromCC: VerificationStatus, conversation: ConversationDetails) {
val currentStatus = conversation.conversation.verificationStatus
val currentStatus = conversation.conversation.mlsVerificationStatus
val newStatus = getActualNewStatus(newStatusFromCC, currentStatus)

if (newStatus == currentStatus) return

conversationRepository.updateVerificationStatus(newStatus, conversation.conversation.id)
conversationRepository.updateMlsVerificationStatus(newStatus, conversation.conversation.id)

if (newStatus == VerificationStatus.DEGRADED || newStatus == VerificationStatus.VERIFIED) {
notifyUserAboutStateChanges(conversation.conversation.id, newStatus)
Expand Down
Loading

0 comments on commit 551cbd8

Please sign in to comment.