Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update conversation Proteus verification status (WPB-1789) #2157

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,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 @@ -18,6 +18,11 @@

package com.wire.kalium.logic.data.conversation

import com.wire.kalium.logic.data.conversation.Conversation.Access
import com.wire.kalium.logic.data.conversation.Conversation.AccessRole
import com.wire.kalium.logic.data.conversation.Conversation.ProtocolInfo
import com.wire.kalium.logic.data.conversation.Conversation.ReceiptMode
import com.wire.kalium.logic.data.conversation.Conversation.Type
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.GroupID
import com.wire.kalium.logic.data.id.PlainId
Expand Down Expand Up @@ -71,7 +76,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 @@ -269,6 +275,11 @@ data class Conversation(
)
}

data class ProteusVerificationData(
val conversationId: ConversationId,
val currentVerificationStatus: VerificationStatus,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion:

Suggested change
val currentVerificationStatus: VerificationStatus,
val verificationStatus: VerificationStatus,

val isActuallyVerified: Boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: what does this mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

conversationId is conversationId, currentVerificationStatus is the verification status of the conversation that is currently stored in DB, isActuallyVerified Boolean if the conversation actually should be verified or not (calculated by checking all the clients of the conversation).
It's a good idea to add some docs :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so isn't better to store the new updated value then return the aggregated value? still I think it's confusing that we're returning two values that one is kinda out dated.

)
}

sealed class ConversationDetails(open val conversation: Conversation) {
Expand Down Expand Up @@ -327,7 +338,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 @@ -81,6 +81,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 @@ -121,7 +122,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 fromDaoModel(daoModel: ConversationViewEntity): Conversation = with(daoModel) {
Expand All @@ -147,7 +149,8 @@ internal class ConversationMapperImpl(
userMessageTimer = userMessageTimer?.toDuration(DurationUnit.MILLISECONDS),
archived = archived,
archivedDateTime = archivedDateTime,
verificationStatus = verificationStatusFromEntity(verificationStatus)
mlsVerificationStatus = verificationStatusFromEntity(mlsVerificationStatus),
proteusVerificationStatus = verificationStatusFromEntity(proteusVerificationStatus)
)
}

Expand All @@ -173,7 +176,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 @@ -371,7 +375,8 @@ internal class ConversationMapperImpl(
userMessageTimer = userMessageTimer?.inWholeMilliseconds,
archived = archived,
archivedInstant = archivedDateTime,
verificationStatus = verificationStatusToEntity(verificationStatus)
mlsVerificationStatus = verificationStatusToEntity(mlsVerificationStatus),
proteusVerificationStatus = verificationStatusToEntity(proteusVerificationStatus)
)
}

Expand Down Expand Up @@ -400,7 +405,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 @@ -451,6 +457,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 @@ -248,7 +248,7 @@ interface ConversationRepository {
domain: String
): Either<CoreFailure, OneOnOneMembers>

suspend fun updateVerificationStatus(
suspend fun updateMlsVerificationStatus(
verificationStatus: Conversation.VerificationStatus,
conversationID: ConversationId
): Either<CoreFailure, Unit>
Expand Down Expand Up @@ -281,6 +281,14 @@ interface ConversationRepository {
* @return **true** if the protocol was changed or **false** if the protocol was unchanged.
*/
suspend fun updateProtocolLocally(conversationId: ConversationId, protocol: Conversation.Protocol): Either<CoreFailure, Boolean>

suspend fun getConversationsProteusVerificationDataByClientId(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
suspend fun getConversationsProteusVerificationDataByClientId(
suspend fun getConversationsProteusVerificationData(

clientId: ClientId
): Either<StorageFailure, List<Conversation.ProteusVerificationData>>

suspend fun updateProteusVerificationStatuses(
Copy link
Contributor

@mchenani mchenani Oct 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking: https://www.grammar-monster.com/plurals/plural_of_status.htm#:~:text=The%20plural%20of%20%22status%22%20is,statuses%22%20since%20the%201960s.)

Suggested change
suspend fun updateProteusVerificationStatuses(
suspend fun updateProteusVerificationStatus(

statusesToUpdate: Map<QualifiedID, Conversation.VerificationStatus>
): Either<StorageFailure, Unit>
}

@Suppress("LongParameterList", "TooManyFunctions", "LargeClass")
Expand Down Expand Up @@ -963,12 +971,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 @@ -990,6 +998,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 @@ -424,4 +424,11 @@ sealed interface MessagePreviewContent {

data 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 @@ -438,6 +438,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 @@ -1435,7 +1435,9 @@ class UserSessionScope internal constructor(
authenticationScope.secondFactorVerificationRepository,
slowSyncRepository,
cachedClientIdClearer,
updateSupportedProtocolsAndResolveOneOnOnes
updateSupportedProtocolsAndResolveOneOnOnes,
conversationRepository,
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 @@ -68,7 +70,9 @@ class ClientScope @OptIn(DelicateKaliumApi::class) internal constructor(
private val secondFactorVerificationRepository: SecondFactorVerificationRepository,
private val slowSyncRepository: SlowSyncRepository,
private val cachedClientIdClearer: CachedClientIdClearer,
private val updateSupportedProtocolsAndResolveOneOnOnes: UpdateSupportedProtocolsAndResolveOneOnOnesUseCase
private val updateSupportedProtocolsAndResolveOneOnOnes: UpdateSupportedProtocolsAndResolveOneOnOnesUseCase,
private val conversationRepository: ConversationRepository,
private val persistMessage: PersistMessageUseCase
) {
@OptIn(DelicateKaliumApi::class)
val register: RegisterClientUseCase
Expand Down Expand Up @@ -148,6 +152,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 {
data 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