Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into feat/persist-legal…
Browse files Browse the repository at this point in the history
…-hold-system-messages
  • Loading branch information
saleniuk committed Dec 6, 2023
2 parents 7694401 + 4e75095 commit 422aacd
Show file tree
Hide file tree
Showing 53 changed files with 1,171 additions and 604 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,16 @@ class MLSClientImpl(
value.handle,
value.displayName,
value.domain,
value.certificate
value.certificate,
toDeviceStatus(value.status)
)

fun toDeviceStatus(value: com.wire.crypto.DeviceStatus) = when (value) {
com.wire.crypto.DeviceStatus.VALID -> CryptoCertificateStatus.VALID
com.wire.crypto.DeviceStatus.EXPIRED -> CryptoCertificateStatus.EXPIRED
com.wire.crypto.DeviceStatus.REVOKED -> CryptoCertificateStatus.REVOKED
}

// TODO: remove later, when CoreCrypto return the groupId instead of Hex value
@Suppress("MagicNumber")
fun toGroupId(hexValue: String): MLSGroupId {
Expand Down Expand Up @@ -366,19 +373,15 @@ class MLSClientImpl(
value.commitDelay?.toLong(),
value.senderClientId?.let { CryptoQualifiedClientId.fromEncodedString(String(it)) },
value.hasEpochChanged,
value.identity?.let {
WireIdentity(it.clientId, it.handle, it.displayName, it.domain, it.certificate)
}
value.identity?.let { toIdentity(it) }
)

fun toDecryptedMessageBundle(value: BufferedDecryptedMessage) = DecryptedMessageBundle(
value.message,
value.commitDelay?.toLong(),
value.senderClientId?.let { CryptoQualifiedClientId.fromEncodedString(String(it)) },
value.hasEpochChanged,
value.identity?.let {
WireIdentity(it.clientId, it.handle, it.displayName, it.domain, it.certificate)
}
value.identity?.let { toIdentity(it) }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,14 @@ data class WireIdentity(
val handle: String,
val displayName: String,
val domain: String,
val certificate: String
val certificate: String,
val status: CryptoCertificateStatus
)

enum class CryptoCertificateStatus {
VALID, EXPIRED, REVOKED;
}

@Suppress("MagicNumber")
data class E2EIQualifiedClientId(
val value: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
*/
package com.wire.kalium.logic.feature.e2ei

import com.wire.kalium.cryptography.CryptoCertificateStatus

actual interface CertificateStatusChecker {
actual fun status(notAfterTimestamp: Long): CertificateStatus
actual fun status(notAfterTimestamp: Long, certificateStatus: CryptoCertificateStatus): CertificateStatus
}

actual class CertificateStatusCheckerImpl : CertificateStatusChecker {
override fun status(notAfterTimestamp: Long): CertificateStatus {
override fun status(notAfterTimestamp: Long, certificateStatus: CryptoCertificateStatus): CertificateStatus {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@
*/
package com.wire.kalium.logic.feature.e2ei

import com.wire.kalium.cryptography.CryptoCertificateStatus

actual interface PemCertificateDecoder {
actual fun decode(certificate: String): E2eiCertificate
actual fun decode(certificate: String, status: CryptoCertificateStatus): E2eiCertificate
}

actual class PemCertificateDecoderImpl actual constructor(
private val x509CertificateGenerator: X509CertificateGenerator,
private val certificateStatusChecker: CertificateStatusChecker
) : PemCertificateDecoder {
override fun decode(certificate: String): E2eiCertificate {
override fun decode(certificate: String, status: CryptoCertificateStatus): E2eiCertificate {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,21 @@
*/
package com.wire.kalium.logic.feature.e2ei

import com.wire.kalium.cryptography.CryptoCertificateStatus
import java.util.Date

actual interface CertificateStatusChecker {
actual fun status(notAfterTimestamp: Long): CertificateStatus
actual fun status(notAfterTimestamp: Long, certificateStatus: CryptoCertificateStatus): CertificateStatus
}

actual class CertificateStatusCheckerImpl : CertificateStatusChecker {
override fun status(notAfterTimestamp: Long): CertificateStatus {
// TODO check for revoked from coreCrypto when API is ready

override fun status(notAfterTimestamp: Long, certificateStatus: CryptoCertificateStatus): CertificateStatus {
val current = Date()
println("current timestap is ${current.time}")
if (current.time >= notAfterTimestamp)
return CertificateStatus.EXPIRED
return CertificateStatus.VALID

return when {
(certificateStatus == CryptoCertificateStatus.REVOKED) -> CertificateStatus.REVOKED
(current.time >= notAfterTimestamp || certificateStatus == CryptoCertificateStatus.EXPIRED) -> CertificateStatus.EXPIRED
else -> CertificateStatus.VALID
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,22 @@
*/
package com.wire.kalium.logic.feature.e2ei

import com.wire.kalium.cryptography.CryptoCertificateStatus
import com.wire.kalium.logic.util.serialNumber

actual interface PemCertificateDecoder {
actual fun decode(certificate: String): E2eiCertificate
actual fun decode(certificate: String, status: CryptoCertificateStatus): E2eiCertificate
}

actual class PemCertificateDecoderImpl actual constructor(
private val x509CertificateGenerator: X509CertificateGenerator,
private val certificateStatusChecker: CertificateStatusChecker
) : PemCertificateDecoder {
override fun decode(certificate: String): E2eiCertificate {
override fun decode(certificate: String, status: CryptoCertificateStatus): E2eiCertificate {
x509CertificateGenerator.generate(certificate.toByteArray()).also {
return E2eiCertificate(
issuer = it.value.issuerX500Principal.name,
status = certificateStatusChecker.status(it.value.notAfter.time),
status = certificateStatusChecker.status(it.value.notAfter.time, status),
serialNumber = it.value.serialNumber.toString(BASE_16).serialNumber(),
certificateDetail = certificate
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ sealed interface CoreFailure {
data object MissingClientRegistration : CoreFailure

/**
* A user has no key packages available which prevents him/her from being added
* Key packages requested not available which prevents them from being added
* to an existing or new conversation.
*/
data class NoKeyPackagesAvailable(val userId: UserId) : CoreFailure
data class NoKeyPackagesAvailable(val failedUserIds: Set<UserId>) : CoreFailure

/**
* It's not allowed to run the application with development API enabled when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ import com.wire.kalium.logic.NetworkFailure
import com.wire.kalium.logic.data.event.EventMapper
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.GroupID
import com.wire.kalium.logic.data.id.SelfTeamIdProvider
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.service.ServiceId
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.di.MapperProvider
import com.wire.kalium.logic.data.id.SelfTeamIdProvider
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.flatMap
import com.wire.kalium.logic.functional.map
Expand Down Expand Up @@ -185,15 +185,95 @@ internal class ConversationGroupRepositoryImpl(
is ConversationEntity.ProtocolInfo.Mixed ->
tryAddMembersToCloudAndStorage(userIdList, conversationId)
.flatMap {
// best effort approach for migrated conversations, no retries
mlsConversationRepository.addMemberToMLSGroup(GroupID(protocol.groupId), userIdList)
}

is ConversationEntity.ProtocolInfo.MLS -> {
mlsConversationRepository.addMemberToMLSGroup(GroupID(protocol.groupId), userIdList)
tryAddMembersToMLSGroup(conversationId, protocol.groupId, userIdList)
}
}
}

/**
* Handle the error cases and retry for claimPackages offline and out of packages.
* Handle error case and retry for sendingCommit unreachable.
*/
private suspend fun tryAddMembersToMLSGroup(
conversationId: ConversationId,
groupId: String,
userIdList: List<UserId>,
failedUsersList: Set<UserId> = emptySet(),
remainingAttempts: Int = 2
): Either<CoreFailure, Unit> {
return when (val addingMemberResult = mlsConversationRepository.addMemberToMLSGroup(GroupID(groupId), userIdList)) {
is Either.Right -> handleMLSMembersAdded(conversationId, userIdList, failedUsersList)
is Either.Left -> {
addingMemberResult.value.handleMLSMembersFailed(
conversationId = conversationId,
groupId = groupId,
userIdList = userIdList,
failedUsersList = failedUsersList,
remainingAttempts = remainingAttempts,
)
}
}
}

private suspend fun CoreFailure.handleMLSMembersFailed(
conversationId: ConversationId,
groupId: String,
userIdList: List<UserId>,
failedUsersList: Set<UserId>,
remainingAttempts: Int,
): Either<CoreFailure, Unit> {
return when {
// claiming key packages offline or out of packages
this is CoreFailure.NoKeyPackagesAvailable && remainingAttempts > 0 -> {
val (validUsers, failedUsers) = userIdList.partition { !this.failedUserIds.contains(it) }
tryAddMembersToMLSGroup(
conversationId = conversationId,
groupId = groupId,
userIdList = validUsers,
failedUsersList = (failedUsersList + failedUsers).toSet(),
remainingAttempts = remainingAttempts - 1
)
}

// sending commit unreachable
this is NetworkFailure.FederatedBackendFailure.RetryableFailure && remainingAttempts > 0 -> {
val (validUsers, failedUsers) = extractValidUsersForRetryableFederationError(userIdList, this)
tryAddMembersToMLSGroup(
conversationId = conversationId,
groupId = groupId,
userIdList = validUsers,
failedUsersList = (failedUsersList + failedUsers).toSet(),
remainingAttempts = remainingAttempts - 1
)
}

else -> {
newGroupConversationSystemMessagesCreator.value.conversationFailedToAddMembers(
conversationId, (failedUsersList + userIdList)
).flatMap {
Either.Left(this)
}
}
}
}

private suspend fun handleMLSMembersAdded(
conversationId: ConversationId,
userIdList: List<UserId>,
failedUsersList: Set<UserId>
): Either<CoreFailure, Unit> {
return newGroupConversationSystemMessagesCreator.value.conversationResolvedMembersAddedAndFailed(
conversationId.toDao(), userIdList, failedUsersList.toList()
).flatMap {
Either.Right(Unit)
}
}

override suspend fun addService(serviceId: ServiceId, conversationId: ConversationId): Either<CoreFailure, Unit> =
wrapStorageRequest { conversationDAO.getConversationProtocolInfo(conversationId.toDao()) }
.flatMap { protocol ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.wire.kalium.logic.data.conversation

import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.data.id.IdMapper
import com.wire.kalium.logic.data.id.toModel
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.di.MapperProvider
import com.wire.kalium.logic.functional.Either
Expand Down Expand Up @@ -59,7 +60,7 @@ internal class NewConversationMembersRepositoryImpl(
}.flatMap {
newGroupConversationSystemMessagesCreator.value.conversationResolvedMembersAddedAndFailed(
conversationId,
conversationResponse,
conversationResponse.members.otherMembers.map { it.id.toModel() },
failedUsersList
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ 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.di.MapperProvider
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.fold
import com.wire.kalium.network.api.base.authenticated.conversation.ConversationResponse
Expand All @@ -47,7 +46,7 @@ internal interface NewGroupConversationSystemMessagesCreator {
suspend fun conversationReadReceiptStatus(conversation: ConversationResponse): Either<CoreFailure, Unit>
suspend fun conversationResolvedMembersAddedAndFailed(
conversationId: ConversationIDEntity,
conversationResponse: ConversationResponse,
validUsers: List<UserId>,
failedUsersList: List<UserId> = emptyList()
): Either<CoreFailure, Unit>

Expand All @@ -64,7 +63,6 @@ internal class NewGroupConversationSystemMessagesCreatorImpl(
private val selfTeamIdProvider: SelfTeamIdProvider,
private val qualifiedIdMapper: QualifiedIdMapper,
private val selfUserId: UserId,
private val memberMapper: MemberMapper = MapperProvider.memberMapper()
) : NewGroupConversationSystemMessagesCreator {

override suspend fun conversationStarted(conversation: ConversationEntity) = run {
Expand Down Expand Up @@ -143,16 +141,14 @@ internal class NewGroupConversationSystemMessagesCreatorImpl(

override suspend fun conversationResolvedMembersAddedAndFailed(
conversationId: ConversationIDEntity,
conversationResponse: ConversationResponse,
validUsers: List<UserId>,
failedUsersList: List<UserId>
): Either<CoreFailure, Unit> = run {
if (conversationResponse.members.otherMembers.isNotEmpty()) {
if (validUsers.isNotEmpty()) {
persistMessage(
Message.System(
id = uuid4().toString(),
content = MessageContent.MemberChange.CreationAdded(
memberMapper.fromApiModel(conversationResponse.members).otherMembers.map { it.id }
),
content = MessageContent.MemberChange.CreationAdded(validUsers.toList()),
conversationId = conversationId.toModel(),
date = DateTimeUtil.currentIsoDateTimeString(),
senderUserId = selfUserId,
Expand All @@ -169,18 +165,22 @@ internal class NewGroupConversationSystemMessagesCreatorImpl(
override suspend fun conversationFailedToAddMembers(
conversationId: ConversationId,
userIdList: Set<UserId>
): Either<CoreFailure, Unit> {
val messageFailedToAddMembers = Message.System(
uuid4().toString(),
MessageContent.MemberChange.FailedToAdd(userIdList.toList()),
conversationId,
DateTimeUtil.currentIsoDateTimeString(),
selfUserId,
Message.Status.Sent,
Message.Visibility.VISIBLE,
expirationData = null
)
return persistMessage(messageFailedToAddMembers)
): Either<CoreFailure, Unit> = run {
if (userIdList.isNotEmpty()) {
persistMessage(
Message.System(
uuid4().toString(),
MessageContent.MemberChange.FailedToAdd(userIdList.toList()),
conversationId,
DateTimeUtil.currentIsoDateTimeString(),
selfUserId,
Message.Status.Sent,
Message.Visibility.VISIBLE,
expirationData = null
)
)
}
Either.Right(Unit)
}

private suspend fun createFailedToAddSystemMessage(conversationId: ConversationIDEntity, failedUsersList: List<UserId>) {
Expand Down
Loading

0 comments on commit 422aacd

Please sign in to comment.