Skip to content

Commit

Permalink
feat: create observer for legal hold request (WPB-5442) (#2243)
Browse files Browse the repository at this point in the history
* feat: handle legal hold events

* chore: address comments

* chore: detekt

* chore: detekt

* feat: create use case to observe legal hold request

* chore: detekt

* chore: detekt

* chore: cleanup

* chore: address comments

* chore: use case documentation
  • Loading branch information
ohassine authored Nov 23, 2023
1 parent 9d7b471 commit 23be862
Show file tree
Hide file tree
Showing 16 changed files with 340 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ package com.wire.kalium.cryptography

import android.util.Base64
import com.wire.cryptobox.CryptoBox
import com.wire.cryptobox.CryptoBox.getFingerprintFromPrekey
import com.wire.cryptobox.CryptoException
import com.wire.kalium.cryptography.exceptions.ProteusException
import io.ktor.util.decodeBase64Bytes
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.coroutines.CoroutineContext
import com.wire.cryptobox.PreKey as CryptoBoxPreKey

@Suppress("TooManyFunctions")
class ProteusClientCryptoBoxImpl constructor(
Expand Down Expand Up @@ -54,6 +57,12 @@ class ProteusClientCryptoBoxImpl constructor(
wrapException { box.getSession(sessionId.value).remoteFingerprint }
}

override suspend fun getFingerprintFromPreKey(preKey: PreKeyCrypto): ByteArray =
withContext(defaultContext) {
val cryptoBoxPreKey = CryptoBoxPreKey(preKey.id, preKey.encodedData.decodeBase64Bytes())
getFingerprintFromPrekey(cryptoBoxPreKey)
}

/**
* Create the crypto files if missing and call box.open
* this must be called only one time
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class ProteusClientCoreCryptoImpl private constructor(private val coreCrypto: Co
return wrapException { coreCrypto.proteusFingerprintRemote(sessionId.value).toByteArray() }
}

override suspend fun getFingerprintFromPreKey(preKey: PreKeyCrypto): ByteArray {
// TODO this is a hack, we need to expose the fingerprint from the core
return "".toByteArray()
}

override suspend fun newPreKeys(from: Int, count: Int): ArrayList<PreKeyCrypto> {
return wrapException {
from.until(from + count).map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.wire.kalium.cryptography

import com.wire.crypto.CoreCrypto
import com.wire.crypto.CoreCryptoException
import com.wire.crypto.client.toByteArray
import com.wire.kalium.cryptography.exceptions.ProteusException
import io.ktor.util.decodeBase64Bytes
import io.ktor.util.encodeBase64
Expand All @@ -45,6 +46,9 @@ class ProteusClientCoreCryptoImpl private constructor(
override suspend fun remoteFingerPrint(sessionId: CryptoSessionId): ByteArray = wrapException {
coreCrypto.proteusFingerprintRemote(sessionId.value).toByteArray()
}
override suspend fun getFingerprintFromPreKey(preKey: PreKeyCrypto): ByteArray = wrapException {
coreCrypto.proteusFingerprintPrekeybundle(preKey.encodedData.decodeBase64Bytes()).toByteArray()
}

override suspend fun newPreKeys(from: Int, count: Int): ArrayList<PreKeyCrypto> {
return wrapException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ interface ProteusClient {
@Throws(ProteusException::class, CancellationException::class)
suspend fun remoteFingerPrint(sessionId: CryptoSessionId): ByteArray

@Throws(ProteusException::class, CancellationException::class)
suspend fun getFingerprintFromPreKey(preKey: PreKeyCrypto): ByteArray

suspend fun newPreKeys(from: Int, count: Int): List<PreKeyCrypto>

@Throws(ProteusException::class, CancellationException::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.wire.kalium.cryptography.externals.PreKeyBundle
import io.ktor.util.InternalAPI
import io.ktor.util.decodeBase64Bytes
import io.ktor.util.encodeBase64
import io.ktor.utils.io.core.toByteArray
import kotlinx.coroutines.await
import org.khronos.webgl.ArrayBuffer
import org.khronos.webgl.Int8Array
Expand Down Expand Up @@ -57,6 +58,11 @@ class ProteusClientCryptoBoxImpl : ProteusClient {
return box.identity.public_key.fingerprint().encodeToByteArray()
}

override suspend fun getFingerprintFromPreKey(preKey: PreKeyCrypto): ByteArray {
// TODO ("we need to expose the fingerprint from the core")
return "".toByteArray()
}

override suspend fun remoteFingerPrint(sessionId: CryptoSessionId): ByteArray {
return box.session_load(sessionId.value).await().fingerprint_remote().encodeToByteArray()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package com.wire.kalium.cryptography

import com.wire.bots.cryptobox.CryptoBox
import com.wire.bots.cryptobox.CryptoBox.getFingerprintFromPrekey
import com.wire.bots.cryptobox.CryptoException
import com.wire.kalium.cryptography.exceptions.ProteusException
import java.io.File
Expand Down Expand Up @@ -63,6 +64,10 @@ class ProteusClientCryptoBoxImpl constructor(
TODO("get session is private in Cryptobox4j")
}

override suspend fun getFingerprintFromPreKey(preKey: PreKeyCrypto): ByteArray = wrapException {
getFingerprintFromPrekey(toPreKey(preKey))
}

override suspend fun newLastResortPreKey(): PreKeyCrypto {
return wrapException { toPreKey(box.newLastPreKey()) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
package com.wire.kalium.logic.configuration

import com.wire.kalium.logic.StorageFailure
import com.wire.kalium.logic.data.conversation.ClientId
import com.wire.kalium.logic.data.featureConfig.MLSMigrationModel
import com.wire.kalium.logic.data.featureConfig.toEntity
import com.wire.kalium.logic.data.featureConfig.toModel
import com.wire.kalium.logic.data.legalhold.LastPreKey
import com.wire.kalium.logic.data.legalhold.LegalHoldRequest
import com.wire.kalium.logic.data.message.SelfDeletionMapper.toSelfDeletionTimerEntity
import com.wire.kalium.logic.data.message.SelfDeletionMapper.toTeamSelfDeleteTimer
import com.wire.kalium.logic.data.message.TeamSettingsSelfDeletionStatus
Expand Down Expand Up @@ -112,6 +115,8 @@ interface UserConfigRepository {
lastPreKeyId: Int,
lastPreKey: String
): Either<StorageFailure, Unit>

fun observeLegalHoldRequest(): Flow<Either<StorageFailure, LegalHoldRequest>>
suspend fun deleteLegalHoldRequest(): Either<StorageFailure, Unit>
}

Expand Down Expand Up @@ -398,7 +403,19 @@ internal class UserConfigDataSource internal constructor(
userConfigDAO.persistLegalHoldRequest(clientId, lastPreKeyId, lastPreKey)
}

override suspend fun deleteLegalHoldRequest(): Either<StorageFailure, Unit> = wrapStorageRequest {
userConfigDAO.clearLegalHoldRequest()
}
override fun observeLegalHoldRequest(): Flow<Either<StorageFailure, LegalHoldRequest>> =
userConfigDAO.observeLegalHoldRequest().wrapStorageRequest().mapRight {
LegalHoldRequest(
clientId = ClientId(it.clientId),
lastPreKey = LastPreKey(
it.lastPreKey.id,
it.lastPreKey.key
)
)
}

override suspend fun deleteLegalHoldRequest(): Either<StorageFailure, Unit> =
wrapStorageRequest {
userConfigDAO.clearLegalHoldRequest()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ package com.wire.kalium.logic.data.legalhold

data class LastPreKey(
val id: Int,
val key: String,
val key: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.legalhold

import com.wire.kalium.logic.data.conversation.ClientId

data class LegalHoldRequest(
val clientId: ClientId,
val lastPreKey: LastPreKey
)
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.datetime.Instant

@Suppress("TooManyFunctions")
interface PreKeyRepository {
/**
* Fetches the IDs of the prekeys currently available on the backend.
Expand Down Expand Up @@ -121,9 +122,11 @@ interface PreKeyRepository {
suspend fun establishSessions(
missingContactClients: Map<UserId, List<ClientId>>
): Either<CoreFailure, UsersWithoutSessions>

suspend fun getFingerprintForPreKey(preKeyCrypto: PreKeyCrypto): Either<CoreFailure, ByteArray>
}

@Suppress("LongParameterList")
@Suppress("LongParameterList", "TooManyFunctions")
class PreKeyDataSource(
private val preKeyApi: PreKeyApi,
private val proteusClientProvider: ProteusClientProvider,
Expand Down Expand Up @@ -218,6 +221,13 @@ class PreKeyDataSource(
}
}

override suspend fun getFingerprintForPreKey(preKeyCrypto: PreKeyCrypto): Either<CoreFailure, ByteArray> =
proteusClientProvider.getOrError().flatMap { proteusClient ->
wrapProteusRequest {
proteusClient.getFingerprintFromPreKey(preKeyCrypto)
}
}

internal suspend fun preKeysOfClientsByQualifiedUsers(
qualifiedIdsMap: Map<UserId, List<ClientId>>
): Either<NetworkFailure, ListPrekeysResponse> = wrapApiRequest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ import com.wire.kalium.logic.feature.featureConfig.handler.SecondFactorPasswordC
import com.wire.kalium.logic.feature.featureConfig.handler.SelfDeletingMessagesConfigHandler
import com.wire.kalium.logic.feature.keypackage.KeyPackageManager
import com.wire.kalium.logic.feature.keypackage.KeyPackageManagerImpl
import com.wire.kalium.logic.feature.legalhold.LegalHoldRequestUseCase
import com.wire.kalium.logic.feature.legalhold.LegalHoldRequestUseCaseImpl
import com.wire.kalium.logic.feature.message.AddSystemMessageToAllConversationsUseCase
import com.wire.kalium.logic.feature.message.AddSystemMessageToAllConversationsUseCaseImpl
import com.wire.kalium.logic.feature.message.EphemeralEventsNotificationManagerImpl
Expand Down Expand Up @@ -1736,6 +1738,12 @@ class UserSessionScope internal constructor(
}
}

val legalHoldRequestUseCase: LegalHoldRequestUseCase
get() = LegalHoldRequestUseCaseImpl(
userConfigRepository = userConfigRepository,
preKeyRepository = preKeyRepository
)

internal val getProxyCredentials: GetProxyCredentialsUseCase
get() = GetProxyCredentialsUseCaseImpl(sessionManager)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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.feature.legalhold

import com.wire.kalium.cryptography.PreKeyCrypto
import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.StorageFailure
import com.wire.kalium.logic.configuration.UserConfigRepository
import com.wire.kalium.logic.data.prekey.PreKeyRepository
import com.wire.kalium.logic.functional.fold
import com.wire.kalium.logic.kaliumLogger
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

/**
* Use case that observes the legal hold request.
*/
interface LegalHoldRequestUseCase {
operator fun invoke(): Flow<LegalHoldRequestObserverResult>
}

internal class LegalHoldRequestUseCaseImpl internal constructor(
val userConfigRepository: UserConfigRepository,
val preKeyRepository: PreKeyRepository
) : LegalHoldRequestUseCase {
override fun invoke(): Flow<LegalHoldRequestObserverResult> =
userConfigRepository.observeLegalHoldRequest().map {
it.fold(
{ failure ->
if (failure is StorageFailure.DataNotFound) {
kaliumLogger.i("No legal hold request found")
LegalHoldRequestObserverResult.NoLegalHoldRequest
} else {
kaliumLogger.i("Legal hold request failure: $failure")
LegalHoldRequestObserverResult.Failure(failure)
}
},
{ request ->
val preKeyCrypto = PreKeyCrypto(request.lastPreKey.id, request.lastPreKey.key)
val result = preKeyRepository.getFingerprintForPreKey(preKeyCrypto)
result.fold(
{ failure ->
kaliumLogger.i("Legal hold request fingerprint failure: $failure")
LegalHoldRequestObserverResult.Failure(failure)
},
{ fingerprint ->
LegalHoldRequestObserverResult.LegalHoldRequestAvailable(
fingerprint
)
}
)
}
)
}
}

sealed class LegalHoldRequestObserverResult {
data class LegalHoldRequestAvailable(val fingerprint: ByteArray) : LegalHoldRequestObserverResult() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as LegalHoldRequestAvailable

return fingerprint.contentEquals(other.fingerprint)
}

override fun hashCode(): Int {
return fingerprint.contentHashCode()
}
}

data object NoLegalHoldRequest : LegalHoldRequestObserverResult()
data class Failure(val failure: CoreFailure) : LegalHoldRequestObserverResult()
}
Loading

0 comments on commit 23be862

Please sign in to comment.