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: Indicate user with valid E2EI certificate (WPB-3228) #2561

Merged
merged 8 commits into from
Jan 11, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import com.wire.kalium.logic.feature.asset.GetAvatarAssetUseCase
import com.wire.kalium.logic.feature.conversation.GetAllContactsNotInConversationUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.EnrollE2EIUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.GetE2eiCertificateUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.GetMembersE2EICertificateStatusesUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusUseCase
import com.wire.kalium.logic.feature.publicuser.GetAllContactsUseCase
import com.wire.kalium.logic.feature.publicuser.GetKnownUserUseCase
import com.wire.kalium.logic.feature.publicuser.RefreshUsersWithoutMetadataUseCase
Expand Down Expand Up @@ -224,4 +226,14 @@ class UserModule {
@Provides
fun provideGetE2EICertificateUseCase(userScope: UserScope): GetE2eiCertificateUseCase =
userScope.getE2EICertificate

@ViewModelScoped
@Provides
fun provideGetUserE2eiCertificateStatusUseCase(userScope: UserScope): GetUserE2eiCertificateStatusUseCase =
userScope.getUserE2eiCertificateStatus

@ViewModelScoped
@Provides
fun provideGetMembersE2EICertificateStatusesUseCase(userScope: UserScope): GetMembersE2EICertificateStatusesUseCase =
userScope.getMembersE2EICertificateStatuses
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ import com.wire.kalium.logic.data.user.OtherUser
import com.wire.kalium.logic.data.user.SelfUser
import com.wire.kalium.logic.data.user.User
import com.wire.kalium.logic.data.user.type.UserType
import com.wire.kalium.logic.feature.e2ei.CertificateStatus
import javax.inject.Inject

class UIParticipantMapper @Inject constructor(
private val userTypeMapper: UserTypeMapper,
private val wireSessionImageLoader: WireSessionImageLoader
) {
fun toUIParticipant(user: User): UIParticipant = with(user) {
fun toUIParticipant(user: User, mlsCertificateStatus: CertificateStatus? = null): UIParticipant = with(user) {
val (userType, connectionState, unavailable) = when (this) {
is OtherUser -> Triple(this.userType, this.connectionStatus, this.isUnavailableUser)
// TODO(refactor): does self user need a type ? to false
Expand All @@ -55,6 +56,7 @@ class UIParticipantMapper @Inject constructor(
botService = (user as? OtherUser)?.botService,
isDefederated = (user is OtherUser && user.defederated),
isProteusVerified = (user is OtherUser && user.isProteusVerified),
isMLSVerified = mlsCertificateStatus == CertificateStatus.VALID,
supportedProtocolList = supportedProtocols.orEmpty().toList()
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.wire.android.R
import com.wire.android.model.Clickable
import com.wire.android.model.UserAvatarData
import com.wire.android.ui.common.ArrowRightIcon
import com.wire.android.ui.common.MLSVerifiedIcon
import com.wire.android.ui.common.ProteusVerifiedIcon
import com.wire.android.ui.common.ProtocolLabel
import com.wire.android.ui.common.RowItemTemplate
Expand Down Expand Up @@ -95,6 +96,7 @@ fun ConversationParticipantItem(
isDeleted = uiParticipant.isDeleted
)

if (uiParticipant.isMLSVerified) MLSVerifiedIcon()
if (uiParticipant.isProteusVerified) ProteusVerifiedIcon()
if (BuildConfig.MLS_SUPPORT_ENABLED && BuildConfig.DEVELOPER_FEATURES_ENABLED) {
uiParticipant.supportedProtocolList.map {
Expand Down Expand Up @@ -144,6 +146,7 @@ fun PreviewGroupConversationParticipantItem() {
false,
UserAvatarData(),
Membership.Guest,
isMLSVerified = true,
isProteusVerified = true,
supportedProtocolList = listOf(SupportedProtocol.PROTEUS, SupportedProtocol.MLS)),
clickable = Clickable(enabled = true) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ data class UIParticipant(
val botService: BotService? = null,
val isDefederated: Boolean = false,
val isProteusVerified: Boolean = false,
val isMLSVerified: Boolean = false,
val supportedProtocolList: List<SupportedProtocol> = listOf()
)
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,23 @@ package com.wire.android.ui.home.conversations.details.participants.usecase
import com.wire.android.mapper.UIParticipantMapper
import com.wire.android.ui.home.conversations.details.participants.model.ConversationParticipantsData
import com.wire.android.ui.home.conversations.name
import com.wire.android.ui.home.conversations.userId
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.kalium.logic.data.conversation.Conversation.Member
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.OtherUser
import com.wire.kalium.logic.data.user.SelfUser
import com.wire.kalium.logic.data.user.type.UserType
import com.wire.kalium.logic.feature.conversation.ObserveConversationMembersUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.GetMembersE2EICertificateStatusesUseCase
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class ObserveParticipantsForConversationUseCase @Inject constructor(
private val observeConversationMembers: ObserveConversationMembersUseCase,
private val getMembersE2EICertificateStatuses: GetMembersE2EICertificateStatusesUseCase,
private val uiParticipantMapper: UIParticipantMapper,
private val dispatchers: DispatcherProvider
) {
Expand All @@ -47,13 +50,21 @@ class ObserveParticipantsForConversationUseCase @Inject constructor(
isAdmin && !isService
}
}

.map { sortedMemberList ->
val allAdminsWithoutServices = sortedMemberList.getOrDefault(true, listOf())
val visibleAdminsWithoutServices = allAdminsWithoutServices.limit(limit)
val allParticipants = sortedMemberList.getOrDefault(false, listOf())
val visibleParticipants = allParticipants.limit(limit)

val visibleUserIds = visibleParticipants.map { it.userId }
.plus(visibleAdminsWithoutServices.map { it.userId })

val mlsVerificationMap = getMembersE2EICertificateStatuses(conversationId, visibleUserIds)
ConversationParticipantsData(
admins = allAdminsWithoutServices.limit(limit).map { uiParticipantMapper.toUIParticipant(it.user) },
participants = allParticipants.limit(limit).map { uiParticipantMapper.toUIParticipant(it.user) },
admins = visibleAdminsWithoutServices
.map { uiParticipantMapper.toUIParticipant(it.user, mlsVerificationMap[it.user.id]) },
participants = visibleParticipants
.map { uiParticipantMapper.toUIParticipant(it.user, mlsVerificationMap[it.user.id]) },
allAdminsCount = allAdminsWithoutServices.size,
allParticipantsCount = allParticipants.size,
isSelfAnAdmin = allAdminsWithoutServices.any { it.user is SelfUser }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import com.wire.android.model.Clickable
import com.wire.android.model.ImageAsset.UserAvatarAsset
import com.wire.android.model.UserAvatarData
import com.wire.android.ui.common.Icon
import com.wire.android.ui.common.MLSVerifiedIcon
import com.wire.android.ui.common.ProteusVerifiedIcon
import com.wire.android.ui.common.UserBadge
import com.wire.android.ui.common.UserProfileAvatar
Expand Down Expand Up @@ -89,6 +90,7 @@ fun UserProfileInfo(
connection: ConnectionState = ConnectionState.ACCEPTED,
delayToShowPlaceholderIfNoAsset: Duration = 200.milliseconds,
isProteusVerified: Boolean = false,
isMLSVerified: Boolean = false,
onSearchConversationMessagesClick: () -> Unit = {},
onConversationMediaClick: () -> Unit = {},
shouldShowSearchButton: Boolean = false
Expand Down Expand Up @@ -181,6 +183,7 @@ fun UserProfileInfo(
else MaterialTheme.wireColorScheme.labelText
)

if (isMLSVerified) MLSVerifiedIcon()
if (isProteusVerified) ProteusVerifiedIcon()
}
Text(
Expand Down Expand Up @@ -280,6 +283,7 @@ fun PreviewUserProfileInfo() {
onUserProfileClick = {},
teamName = "Wire",
connection = ConnectionState.ACCEPTED,
isProteusVerified = true
isProteusVerified = true,
isMLSVerified = true
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ private fun TopBarCollapsing(
modifier = Modifier.padding(bottom = dimensions().spacing16x),
connection = targetState.connectionState,
isProteusVerified = targetState.isProteusVerified,
isMLSVerified = targetState.isMLSVerified,
onSearchConversationMessagesClick = onSearchConversationMessagesClick,
shouldShowSearchButton = state.shouldShowSearchButton(),
onConversationMediaClick = onConversationMediaClick
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ import com.wire.kalium.logic.feature.conversation.UpdateConversationArchivedStat
import com.wire.kalium.logic.feature.conversation.UpdateConversationMemberRoleResult
import com.wire.kalium.logic.feature.conversation.UpdateConversationMemberRoleUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationMutedStatusUseCase
import com.wire.kalium.logic.feature.e2ei.CertificateStatus
import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusResult
import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusUseCase
import com.wire.kalium.logic.feature.user.GetUserInfoResult
import com.wire.kalium.logic.feature.user.ObserveUserInfoUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
Expand Down Expand Up @@ -101,6 +104,7 @@ class OtherUserProfileScreenViewModel @Inject constructor(
private val fetchUsersClients: FetchUsersClientsFromRemoteUseCase,
private val clearConversationContentUseCase: ClearConversationContentUseCase,
private val updateConversationArchivedStatus: UpdateConversationArchivedStatusUseCase,
private val getUserE2eiCertificateStatus: GetUserE2eiCertificateStatusUseCase,
savedStateHandle: SavedStateHandle
) : ViewModel(), OtherUserProfileEventsHandler, OtherUserProfileBottomSheetEventsHandler {

Expand All @@ -127,6 +131,16 @@ class OtherUserProfileScreenViewModel @Inject constructor(
init {
observeUserInfoAndUpdateViewState()
persistClients()
getMLSVerificationStatus()
}

private fun getMLSVerificationStatus() {
viewModelScope.launch(dispatchers.io()) {
val isMLSVerified = getUserE2eiCertificateStatus(userId).let {
it is GetUserE2eiCertificateStatusResult.Success && it.status == CertificateStatus.VALID
}
state = state.copy(isMLSVerified = isMLSVerified)
}
}

override fun observeClientList() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ data class OtherUserProfileState(
val conversationSheetContent: ConversationSheetContent? = null,
val otherUserDevices: List<Device> = listOf(),
val blockingState: BlockingState = BlockingState.CAN_NOT_BE_BLOCKED,
val isProteusVerified: Boolean = false
val isProteusVerified: Boolean = false,
val isMLSVerified: Boolean = false
) {
fun updateMuteStatus(status: MutedConversationStatus): OtherUserProfileState {
return conversationSheetContent?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.wire.kalium.logic.data.conversation.MemberDetails
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.type.UserType
import com.wire.kalium.logic.feature.conversation.ObserveConversationMembersUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.GetMembersE2EICertificateStatusesUseCase
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
Expand All @@ -37,13 +38,14 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.internal.assertEquals
import org.junit.jupiter.api.Test

@OptIn(ExperimentalCoroutinesApi::class)
class ObserveParticipantsForConversationUseCaseTest {

@Test
fun `given a group members, when solving the participants list with limit, then limited sizes are passed`() = runTest {
fun givenGroupMembers_whenSolvingTheParticipantsListWithLimit_thenLimitedSizesArePassed() = runTest {
// Given
val limit = 4
val members = buildList {
Expand All @@ -57,13 +59,13 @@ class ObserveParticipantsForConversationUseCaseTest {
// When - Then
useCase(ConversationId("", ""), limit).test {
val data = awaitItem()
assert(data.participants.size == limit)
assert(data.allParticipantsCount == members.size)
assertEquals(limit, data.participants.size)
assertEquals(members.size, data.allParticipantsCount)
}
}

@Test
fun `given a group members, when solving the participants list without limit, then all lists are passed`() = runTest {
fun givenGroupMembers_whenSolvingTheParticipantsListWithoutLimit_thenAllListsArePassed() = runTest {
// Given
val members: List<MemberDetails> = buildList {
for (i in 1..20) {
Expand All @@ -86,13 +88,18 @@ internal class ObserveParticipantsForConversationUseCaseArrangement {

@MockK
lateinit var observeConversationMembersUseCase: ObserveConversationMembersUseCase

@MockK
lateinit var getMembersE2EICertificateStatuses: GetMembersE2EICertificateStatusesUseCase

@MockK
private lateinit var wireSessionImageLoader: WireSessionImageLoader
private val uIParticipantMapper by lazy { UIParticipantMapper(UserTypeMapper(), wireSessionImageLoader) }
private val conversationMembersChannel = Channel<List<MemberDetails>>(capacity = Channel.UNLIMITED)
private val useCase by lazy {
ObserveParticipantsForConversationUseCase(
observeConversationMembersUseCase,
getMembersE2EICertificateStatuses,
uIParticipantMapper,
dispatchers = TestDispatcherProvider()
)
Expand All @@ -103,6 +110,7 @@ internal class ObserveParticipantsForConversationUseCaseArrangement {
MockKAnnotations.init(this, relaxUnitFun = true)
// Default empty values
coEvery { observeConversationMembersUseCase(any()) } returns flowOf()
coEvery { getMembersE2EICertificateStatuses(any(), any()) } returns mapOf()
}

suspend fun withConversationParticipantsUpdate(members: List<MemberDetails>): ObserveParticipantsForConversationUseCaseArrangement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ import com.wire.kalium.logic.feature.conversation.UpdateConversationArchivedStat
import com.wire.kalium.logic.feature.conversation.UpdateConversationMemberRoleResult
import com.wire.kalium.logic.feature.conversation.UpdateConversationMemberRoleUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationMutedStatusUseCase
import com.wire.kalium.logic.feature.e2ei.CertificateStatus
import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusResult
import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusUseCase
import com.wire.kalium.logic.feature.user.GetSelfUserUseCase
import com.wire.kalium.logic.feature.user.GetUserInfoResult
import com.wire.kalium.logic.feature.user.ObserveUserInfoUseCase
Expand Down Expand Up @@ -102,6 +105,9 @@ internal class OtherUserProfileViewModelArrangement {
@MockK
lateinit var updateConversationArchivedStatus: UpdateConversationArchivedStatusUseCase

@MockK
lateinit var getUserE2eiCertificateStatus: GetUserE2eiCertificateStatusUseCase

private val viewModel by lazy {
OtherUserProfileScreenViewModel(
TestDispatcherProvider(),
Expand All @@ -119,6 +125,7 @@ internal class OtherUserProfileViewModelArrangement {
fetchUsersClientsFromRemote,
clearConversationContent,
updateConversationArchivedStatus,
getUserE2eiCertificateStatus,
savedStateHandle
)
}
Expand Down Expand Up @@ -147,6 +154,7 @@ internal class OtherUserProfileViewModelArrangement {
coEvery { getOneToOneConversation(USER_ID) } returns flowOf(
GetOneToOneConversationUseCase.Result.Success(OtherUserProfileScreenViewModelTest.CONVERSATION)
)
coEvery { getUserE2eiCertificateStatus.invoke(any()) } returns GetUserE2eiCertificateStatusResult.Success(CertificateStatus.VALID)
}

suspend fun withBlockUserResult(result: BlockUserResult) = apply {
Expand Down
2 changes: 1 addition & 1 deletion kalium
Submodule kalium updated 43 files
+1 −1 gradle/libs.versions.toml
+1 −0 logic/src/androidMain/kotlin/com/wire/kalium/logic/CoreLogic.kt
+4 −1 logic/src/androidMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt
+3 −0 logic/src/androidMain/kotlin/com/wire/kalium/logic/feature/UserSessionScopeProviderImpl.kt
+1 −0 logic/src/appleMain/kotlin/com/wire/kalium/logic/CoreLogic.kt
+4 −1 logic/src/appleMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt
+3 −0 logic/src/appleMain/kotlin/com/wire/kalium/logic/feature/UserSessionScopeProviderImpl.kt
+44 −25 logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/feature/call/CallManagerImpl.kt
+8 −2 logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreLogic.kt
+3 −1 logic/src/commonMain/kotlin/com/wire/kalium/logic/GlobalKaliumScope.kt
+42 −0 logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt
+20 −1 logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt
+4 −1 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt
+45 −0 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/auth/LogoutCallbackManager.kt
+3 −2 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/auth/LogoutUseCase.kt
+54 −0 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/ShouldRemoteMuteChecker.kt
+68 −0 ...rc/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetMembersE2EICertificateStatusesUseCase.kt
+55 −0 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/GetUserE2EICertificateUseCase.kt
+2 −1 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/session/CurrentSessionFlowUseCase.kt
+19 −4 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt
+1 −1 logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncCriteriaProvider.kt
+86 −0 logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepositoryTest.kt
+22 −5 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/auth/LogoutUseCaseTest.kt
+124 −0 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/ShouldRemoteMuteCheckerTest.kt
+0 −2 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/ClientFingerprintUseCaseTest.kt
+131 −0 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetMembersE2EICertificateStatusesUseCaseTest.kt
+131 −0 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/GetUserE2eiCertificateStatusUseCaseTest.kt
+21 −0 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/session/CurrentSessionFlowUseCaseTest.kt
+18 −0 logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/MLSConversationRepositoryArrangement.kt
+51 −0 logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/mls/PemCertificateDecoderArrangement.kt
+1 −0 logic/src/jvmMain/kotlin/com/wire/kalium/logic/CoreLogic.kt
+4 −1 logic/src/jvmMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt
+3 −0 logic/src/jvmMain/kotlin/com/wire/kalium/logic/feature/UserSessionScopeProviderImpl.kt
+4 −3 monkeys/src/main/db_monkeys/com/wire/kalium/monkeys/db/ExecutionEvent.sq
+4 −6 monkeys/src/main/kotlin/com/wire/kalium/monkeys/MonkeyApplication.kt
+5 −1 monkeys/src/main/kotlin/com/wire/kalium/monkeys/ReplayApplication.kt
+6 −2 monkeys/src/main/kotlin/com/wire/kalium/monkeys/conversation/Monkey.kt
+3 −1 monkeys/src/main/kotlin/com/wire/kalium/monkeys/model/EventData.kt
+1 −1 monkeys/src/main/kotlin/com/wire/kalium/monkeys/pool/MonkeyPool.kt
+2 −1 monkeys/src/main/kotlin/com/wire/kalium/monkeys/storage/PostgresStorage.kt
+9 −0 persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq
+2 −0 persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt
+13 −0 persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt
Loading