Skip to content

Commit

Permalink
feat: create password protected guest link (#1986)
Browse files Browse the repository at this point in the history
* feat: add use case to decide if a user can create password protected invite links [WPB-1531] (#1950)

* CanCreatePasswordProtectedLinksUseCase

* docs

* detekt

* test

* Update logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/guestroomlink/CanCreatePasswordProtectedLinksUseCase.kt

Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>

---------

Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>
Co-authored-by: Oussama Hassine <oussama.has100@gmail.com>

* feat: handle code update and delete events (#1960)

* CanCreatePasswordProtectedLinksUseCase

* docs

* detekt

* test

* feat: handle code update and delete event

* test

* code delete code

* inject handler

* detekt

* add fake value for is password protected

* add has password to event DTO

* missing any

* fix test

* detekt

* db migration

* typo in db migration

* Update logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/CodeDeletedHandler.kt

Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>

* Update logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/CodeUpdateHandlerTest.kt

Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>

* file name

* fix(tests): fix e2ei mock order timing

---------

Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>
Co-authored-by: Mojtaba Chenani <chenani@outlook.com>

* feat: create password protected conv invite link (#1982)

* CanCreatePasswordProtectedLinksUseCase

* docs

* detekt

* test

* feat: handle code update and delete event

* test

* code delete code

* inject handler

* detekt

* add fake value for is password protected

* add has password to event DTO

* missing any

* fix test

* detekt

* db migration

* typo in db migration

* Update logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/CodeDeletedHandler.kt

Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>

* Update logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/CodeUpdateHandlerTest.kt

Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>

* file name

* fix(tests): fix e2ei mock order timing

* support api v4 for password protected invite links

* fix: unresolved reference

* fix merge issues

* feat: generate password protected guest link

* add test

* detekt

* fix test

---------

Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>
Co-authored-by: Mojtaba Chenani <chenani@outlook.com>

* feat: propagate if a guest link is password protected (#1985)

* CanCreatePasswordProtectedLinksUseCase

* docs

* detekt

* test

* feat: handle code update and delete event

* test

* code delete code

* inject handler

* detekt

* add fake value for is password protected

* add has password to event DTO

* missing any

* fix test

* detekt

* db migration

* typo in db migration

* Update logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/CodeDeletedHandler.kt

Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>

* Update logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/CodeUpdateHandlerTest.kt

Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>

* file name

* fix(tests): fix e2ei mock order timing

* support api v4 for password protected invite links

* fix: unresolved reference

* fix merge issues

* feat: generate password protected guest link

* add test

* detekt

* fix test

* feat: propagate if a guest link is password protected

---------

Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>
Co-authored-by: Mojtaba Chenani <chenani@outlook.com>

* merge issue

---------

Co-authored-by: Alexandre Ferris <ferris.alexandre@gmail.com>
Co-authored-by: Oussama Hassine <oussama.has100@gmail.com>
Co-authored-by: Mojtaba Chenani <chenani@outlook.com>
  • Loading branch information
4 people authored Aug 18, 2023
1 parent 856da30 commit 23ccfbc
Show file tree
Hide file tree
Showing 42 changed files with 1,262 additions and 168 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,20 @@ class ServerConfigMapperImpl(
)
}

sealed class CommonApiVersionType(open val version: Int) {
object New : CommonApiVersionType(NEW_API_VERSION_NUMBER)
object Unknown : CommonApiVersionType(UNKNOWN_API_VERSION_NUMBER)
data class Valid(override val version: Int) : CommonApiVersionType(version)
sealed interface CommonApiVersionType {
val version: Int

data object New : CommonApiVersionType {
override val version: Int
get() = NEW_API_VERSION_NUMBER
}

data object Unknown : CommonApiVersionType {
override val version: Int
get() = UNKNOWN_API_VERSION_NUMBER
}

data class Valid(override val version: Int) : CommonApiVersionType

companion object {
const val NEW_API_VERSION_NUMBER = -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,21 @@ import com.wire.kalium.logic.sync.receiver.conversation.ConversationMessageTimer
import com.wire.kalium.logic.sync.receiver.conversation.MemberJoinEventHandler
import com.wire.kalium.logic.sync.receiver.conversation.MemberLeaveEventHandler
import com.wire.kalium.logic.wrapApiRequest
import com.wire.kalium.logic.wrapNullableFlowStorageRequest
import com.wire.kalium.logic.wrapStorageRequest
import com.wire.kalium.network.api.base.authenticated.conversation.AddConversationMembersRequest
import com.wire.kalium.network.api.base.authenticated.conversation.AddServiceRequest
import com.wire.kalium.network.api.base.authenticated.conversation.ConversationApi
import com.wire.kalium.network.api.base.authenticated.conversation.ConversationMemberAddedResponse
import com.wire.kalium.network.api.base.authenticated.conversation.ConversationMemberRemovedResponse
import com.wire.kalium.network.api.base.authenticated.conversation.model.ConversationCodeInfo
import com.wire.kalium.network.api.base.authenticated.notification.EventContentDTO
import com.wire.kalium.network.api.base.model.ServiceAddedResponse
import com.wire.kalium.persistence.dao.conversation.ConversationDAO
import com.wire.kalium.persistence.dao.conversation.ConversationEntity
import com.wire.kalium.persistence.dao.message.LocalId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

interface ConversationGroupRepository {
suspend fun createGroupConversation(
Expand All @@ -72,9 +75,13 @@ interface ConversationGroupRepository {
): Either<NetworkFailure, ConversationMemberAddedResponse>

suspend fun fetchLimitedInfoViaInviteCode(code: String, key: String): Either<NetworkFailure, ConversationCodeInfo>
suspend fun generateGuestRoomLink(conversationId: ConversationId): Either<NetworkFailure, Unit>
suspend fun generateGuestRoomLink(
conversationId: ConversationId,
password: String?
): Either<NetworkFailure, EventContentDTO.Conversation.CodeUpdated>

suspend fun revokeGuestRoomLink(conversationId: ConversationId): Either<NetworkFailure, Unit>
suspend fun observeGuestRoomLink(conversationId: ConversationId): Flow<String?>
suspend fun observeGuestRoomLink(conversationId: ConversationId): Flow<Either<CoreFailure, ConversationGuestLink?>>
suspend fun updateMessageTimer(conversationId: ConversationId, messageTimer: Long?): Either<CoreFailure, Unit>
}

Expand Down Expand Up @@ -188,7 +195,13 @@ internal class ConversationGroupRepositoryImpl(
)
}.onSuccess { response ->
if (response is ServiceAddedResponse.Changed) {
memberJoinEventHandler.handle(eventMapper.conversationMemberJoin(LocalId.generate(), response.event, true))
memberJoinEventHandler.handle(
eventMapper.conversationMemberJoin(
LocalId.generate(),
response.event,
true
)
)
}
}.map { Unit }
}
Expand Down Expand Up @@ -278,7 +291,10 @@ internal class ConversationGroupRepositoryImpl(
}
} else {
// when removing a member from an MLS group, don't need to call the api
mlsConversationRepository.removeMembersFromMLSGroup(GroupID(protocol.groupId), listOf(userId))
mlsConversationRepository.removeMembersFromMLSGroup(
GroupID(protocol.groupId),
listOf(userId)
)
}
}
}
Expand Down Expand Up @@ -314,37 +330,54 @@ internal class ConversationGroupRepositoryImpl(
}
}

override suspend fun fetchLimitedInfoViaInviteCode(code: String, key: String): Either<NetworkFailure, ConversationCodeInfo> =
override suspend fun fetchLimitedInfoViaInviteCode(
code: String,
key: String
): Either<NetworkFailure, ConversationCodeInfo> =
wrapApiRequest { conversationApi.fetchLimitedInformationViaCode(code, key) }

private suspend fun deleteMemberFromCloudAndStorage(userId: UserId, conversationId: ConversationId) =
wrapApiRequest {
conversationApi.removeMember(userId.toApi(), conversationId.toApi())
}.onSuccess { response ->
if (response is ConversationMemberRemovedResponse.Changed) {
memberLeaveEventHandler.handle(eventMapper.conversationMemberLeave(LocalId.generate(), response.event, false))
memberLeaveEventHandler.handle(
eventMapper.conversationMemberLeave(
LocalId.generate(),
response.event,
false
)
)
}
}.map { }

override suspend fun generateGuestRoomLink(conversationId: ConversationId): Either<NetworkFailure, Unit> =
override suspend fun generateGuestRoomLink(
conversationId: ConversationId,
password: String?
): Either<NetworkFailure, EventContentDTO.Conversation.CodeUpdated> =
wrapApiRequest {
conversationApi.generateGuestRoomLink(conversationId.toApi())
}.onSuccess {
it.data?.let { data -> conversationDAO.updateGuestRoomLink(conversationId.toDao(), data.uri) }
it.uri?.let { link -> conversationDAO.updateGuestRoomLink(conversationId.toDao(), link) }
}.map { Either.Right(Unit) }
conversationApi.generateGuestRoomLink(conversationId.toApi(), password)
}

override suspend fun revokeGuestRoomLink(conversationId: ConversationId): Either<NetworkFailure, Unit> =
wrapApiRequest {
conversationApi.revokeGuestRoomLink(conversationId.toApi())
}.onSuccess {
conversationDAO.updateGuestRoomLink(conversationId.toDao(), null)
}.map { }
wrapStorageRequest {
conversationDAO.updateGuestRoomLink(conversationId.toDao(), null, false)
}
}

override suspend fun observeGuestRoomLink(conversationId: ConversationId): Flow<String?> =
conversationDAO.observeGuestRoomLinkByConversationId(conversationId.toDao())
override suspend fun observeGuestRoomLink(conversationId: ConversationId): Flow<Either<CoreFailure, ConversationGuestLink?>> =
wrapNullableFlowStorageRequest {
conversationDAO.observeGuestRoomLinkByConversationId(conversationId.toDao())
.map { it?.let { ConversationGuestLink(it.link, it.isPasswordProtected) } }
}

override suspend fun updateMessageTimer(conversationId: ConversationId, messageTimer: Long?): Either<CoreFailure, Unit> =
override suspend fun updateMessageTimer(
conversationId: ConversationId,
messageTimer: Long?
): Either<CoreFailure, Unit> =
wrapApiRequest { conversationApi.updateMessageTimer(conversationId.toApi(), messageTimer) }
.onSuccess {
conversationMessageTimerEventHandler.handle(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Wire
* Copyright (C) 2023 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

data class ConversationGuestLink(
val link: String,
val isPasswordProtected: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ sealed class Event(open val id: String, open val transient: Boolean) {
return "${toLogMap().toJsonElement()}"
}

open fun toLogMap(): Map<String, Any?> = mapOf(typeKey to "Event.Unknown")
abstract fun toLogMap(): Map<String, Any?>

sealed class Conversation(
id: String,
Expand Down Expand Up @@ -322,6 +322,26 @@ sealed class Event(open val id: String, open val transient: Boolean) {
timestampIsoKey to timestampIso
)
}

data class CodeUpdated(
override val id: String,
override val conversationId: ConversationId,
override val transient: Boolean,
val key: String,
val code: String,
val uri: String,
val isPasswordProtected: Boolean,
) : Conversation(id, transient, conversationId) {
override fun toLogMap(): Map<String, Any?> = mapOf(typeKey to "Conversation.CodeUpdated")
}

data class CodeDeleted(
override val id: String,
override val conversationId: ConversationId,
override val transient: Boolean,
) : Conversation(id, transient, conversationId) {
override fun toLogMap(): Map<String, Any?> = mapOf(typeKey to "Conversation.CodeDeleted")
}
}

sealed class Team(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class EventMapper(
is EventContentDTO.UserProperty.PropertiesDeleteDTO -> deleteUserProperties(id, eventContentDTO, transient)
is EventContentDTO.Conversation.ReceiptModeUpdate -> conversationReceiptModeUpdate(id, eventContentDTO, transient)
is EventContentDTO.Conversation.MessageTimerUpdate -> conversationMessageTimerUpdate(id, eventContentDTO, transient)
is EventContentDTO.Conversation.CodeDeleted -> conversationCodeDeleted(id, eventContentDTO, transient)
is EventContentDTO.Conversation.CodeUpdated -> conversationCodeUpdated(id, eventContentDTO, transient)
is EventContentDTO.Federation -> federationTerminated(id, eventContentDTO, transient)
}

Expand All @@ -109,6 +111,30 @@ class EventMapper(
)
}

private fun conversationCodeDeleted(
id: String,
event: EventContentDTO.Conversation.CodeDeleted,
transient: Boolean
): Event.Conversation.CodeDeleted = Event.Conversation.CodeDeleted(
id = id,
transient = transient,
conversationId = event.qualifiedConversation.toModel()
)

private fun conversationCodeUpdated(
id: String,
event: EventContentDTO.Conversation.CodeUpdated,
transient: Boolean
): Event.Conversation.CodeUpdated = Event.Conversation.CodeUpdated(
id = id,
key = event.data.key,
code = event.data.code,
uri = event.data.uri,
isPasswordProtected = event.data.hasPassword,
conversationId = event.qualifiedConversation.toModel(),
transient = transient
)

@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
fun unknown(
id: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,13 +318,17 @@ import com.wire.kalium.logic.sync.receiver.conversation.message.NewMessageEventH
import com.wire.kalium.logic.sync.receiver.conversation.message.NewMessageEventHandlerImpl
import com.wire.kalium.logic.sync.receiver.conversation.message.ProteusMessageUnpacker
import com.wire.kalium.logic.sync.receiver.conversation.message.ProteusMessageUnpackerImpl
import com.wire.kalium.logic.sync.receiver.handler.CodeDeletedHandler
import com.wire.kalium.logic.sync.receiver.handler.ButtonActionConfirmationHandler
import com.wire.kalium.logic.sync.receiver.handler.ButtonActionConfirmationHandlerImpl
import com.wire.kalium.logic.sync.receiver.handler.ClearConversationContentHandlerImpl
import com.wire.kalium.logic.sync.receiver.handler.CodeUpdateHandlerImpl
import com.wire.kalium.logic.sync.receiver.handler.CodeUpdatedHandler
import com.wire.kalium.logic.sync.receiver.handler.CodeDeletedHandlerImpl
import com.wire.kalium.logic.sync.receiver.handler.MessageTextEditHandlerImpl
import com.wire.kalium.logic.sync.receiver.handler.DeleteForMeHandlerImpl
import com.wire.kalium.logic.sync.receiver.handler.DeleteMessageHandlerImpl
import com.wire.kalium.logic.sync.receiver.handler.LastReadContentHandlerImpl
import com.wire.kalium.logic.sync.receiver.handler.MessageTextEditHandlerImpl
import com.wire.kalium.logic.sync.receiver.handler.ReceiptMessageHandlerImpl
import com.wire.kalium.logic.sync.slow.SlowSlowSyncCriteriaProviderImpl
import com.wire.kalium.logic.sync.slow.SlowSyncCriteriaProvider
Expand Down Expand Up @@ -375,11 +379,12 @@ class UserSessionScope internal constructor(
private var _clientId: ClientId? = null

@OptIn(DelicateKaliumApi::class) // Use the uncached client ID in order to create the cache itself.
private suspend fun clientId(): Either<CoreFailure, ClientId> = if (_clientId != null) Either.Right(_clientId!!) else {
clientRepository.currentClientId().onSuccess {
_clientId = it
private suspend fun clientId(): Either<CoreFailure, ClientId> =
if (_clientId != null) Either.Right(_clientId!!) else {
clientRepository.currentClientId().onSuccess {
_clientId = it
}
}
}

private val cachedClientIdClearer: CachedClientIdClearer = object : CachedClientIdClearer {
override fun invoke() {
Expand Down Expand Up @@ -457,7 +462,11 @@ class UserSessionScope internal constructor(
)

private val userConfigRepository: UserConfigRepository
get() = UserConfigDataSource(userStorage.preferences.userConfigStorage, userStorage.database.userConfigDAO, kaliumConfigs)
get() = UserConfigDataSource(
userStorage.preferences.userConfigStorage,
userStorage.database.userConfigDAO,
kaliumConfigs
)

private val userPropertyRepository: UserPropertyRepository
get() = UserPropertyDataSource(
Expand Down Expand Up @@ -907,7 +916,9 @@ class UserSessionScope internal constructor(

private val apiMigrationManager
get() = ApiMigrationManager(
sessionManager.serverConfig().metaData.commonApiVersion.version, userStorage.database.metadataDAO, apiMigrations
sessionManager.serverConfig().metaData.commonApiVersion.version,
userStorage.database.metadataDAO,
apiMigrations
)

private val eventRepository: EventRepository
Expand Down Expand Up @@ -1094,7 +1105,10 @@ class UserSessionScope internal constructor(
get() = MemberLeaveEventHandlerImpl(
userStorage.database.memberDAO, userRepository, persistMessage, updateConversationClientsForCurrentCall
)
private val memberChangeHandler: MemberChangeEventHandler get() = MemberChangeEventHandlerImpl(conversationRepository)
private val memberChangeHandler: MemberChangeEventHandler
get() = MemberChangeEventHandlerImpl(
conversationRepository
)
private val mlsWelcomeHandler: MLSWelcomeEventHandler
get() = MLSWelcomeEventHandlerImpl(
mlsClientProvider, userStorage.database.conversationDAO, conversationRepository
Expand All @@ -1116,6 +1130,16 @@ class UserSessionScope internal constructor(
persistMessage = persistMessage
)

private val conversationCodeUpdateHandler: CodeUpdatedHandler
get() = CodeUpdateHandlerImpl(
conversationDAO = userStorage.database.conversationDAO
)

private val conversationCodeDeletedHandler: CodeDeletedHandler
get() = CodeDeletedHandlerImpl(
conversationDAO = userStorage.database.conversationDAO
)

private val conversationEventReceiver: ConversationEventReceiver by lazy {
ConversationEventReceiverImpl(
newMessageHandler,
Expand All @@ -1127,13 +1151,21 @@ class UserSessionScope internal constructor(
mlsWelcomeHandler,
renamedConversationHandler,
receiptModeUpdateEventHandler,
conversationMessageTimerEventHandler
conversationMessageTimerEventHandler,
conversationCodeUpdateHandler,
conversationCodeDeletedHandler
)
}

private val userEventReceiver: UserEventReceiver
get() = UserEventReceiverImpl(
clientRepository, connectionRepository, conversationRepository, userRepository, logout, userId, clientIdProvider
clientRepository,
connectionRepository,
conversationRepository,
userRepository,
logout,
userId,
clientIdProvider
)

private val userPropertiesEventReceiver: UserPropertiesEventReceiver
Expand Down Expand Up @@ -1216,7 +1248,6 @@ class UserSessionScope internal constructor(
syncManager,
mlsConversationRepository,
clientIdProvider,
assetRepository,
messages.messageSender,
teamRepository,
userId,
Expand All @@ -1228,6 +1259,8 @@ class UserSessionScope internal constructor(
renamedConversationHandler,
qualifiedIdMapper,
team.isSelfATeamMember,
globalScope.serverConfigRepository,
userStorage,
this
)

Expand Down Expand Up @@ -1332,7 +1365,10 @@ class UserSessionScope internal constructor(
val observeFileSharingStatus: ObserveFileSharingStatusUseCase
get() = ObserveFileSharingStatusUseCaseImpl(userConfigRepository)

val getGuestRoomLinkFeature: GetGuestRoomLinkFeatureStatusUseCase get() = GetGuestRoomLinkFeatureStatusUseCaseImpl(userConfigRepository)
val getGuestRoomLinkFeature: GetGuestRoomLinkFeatureStatusUseCase
get() = GetGuestRoomLinkFeatureStatusUseCaseImpl(
userConfigRepository
)

val markGuestLinkFeatureFlagAsNotChanged: MarkGuestLinkFeatureFlagAsNotChangedUseCase
get() = MarkGuestLinkFeatureFlagAsNotChangedUseCaseImpl(userConfigRepository)
Expand Down
Loading

0 comments on commit 23ccfbc

Please sign in to comment.