diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/UserConfigRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/UserConfigRepository.kt index 738df2ad65f..1e386fa5bee 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/UserConfigRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/UserConfigRepository.kt @@ -122,6 +122,14 @@ interface UserConfigRepository { suspend fun observeLegalHoldChangeNotified(): Flow> suspend fun setShouldUpdateClientLegalHoldCapability(shouldUpdate: Boolean): Either suspend fun shouldUpdateClientLegalHoldCapability(): Boolean +<<<<<<< HEAD +======= + suspend fun setCRLExpirationTime(url: String, timestamp: ULong) + suspend fun getCRLExpirationTime(url: String): ULong? + suspend fun observeCertificateExpirationTime(url: String): Flow> + suspend fun setShouldNotifyForRevokedCertificate(shouldNotify: Boolean) + suspend fun observeShouldNotifyForRevokedCertificate(): Flow> +>>>>>>> 35d3229ed7 (feat: Observe self client certificate revocation (WPB-6145) (#2384)) } @Suppress("TooManyFunctions") @@ -434,4 +442,23 @@ internal class UserConfigDataSource internal constructor( override suspend fun shouldUpdateClientLegalHoldCapability(): Boolean = userConfigDAO.shouldUpdateClientLegalHoldCapability() +<<<<<<< HEAD +======= + + override suspend fun setCRLExpirationTime(url: String, timestamp: ULong) { + userConfigDAO.setCRLExpirationTime(url, timestamp) + } + + override suspend fun getCRLExpirationTime(url: String): ULong? = + userConfigDAO.getCRLsPerDomain(url) + + override suspend fun observeCertificateExpirationTime(url: String): Flow> = + userConfigDAO.observeCertificateExpirationTime(url).wrapStorageRequest() + override suspend fun setShouldNotifyForRevokedCertificate(shouldNotify: Boolean) { + userConfigDAO.setShouldNotifyForRevokedCertificate(shouldNotify) + } + + override suspend fun observeShouldNotifyForRevokedCertificate(): Flow> = + userConfigDAO.observeShouldNotifyForRevokedCertificate().wrapStorageRequest() +>>>>>>> 35d3229ed7 (feat: Observe self client certificate revocation (WPB-6145) (#2384)) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt index c7b1a7036bd..6760053cf5b 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt @@ -295,6 +295,8 @@ import com.wire.kalium.logic.feature.user.ObserveE2EIRequiredUseCase import com.wire.kalium.logic.feature.user.ObserveE2EIRequiredUseCaseImpl import com.wire.kalium.logic.feature.user.ObserveFileSharingStatusUseCase import com.wire.kalium.logic.feature.user.ObserveFileSharingStatusUseCaseImpl +import com.wire.kalium.logic.feature.user.e2ei.ObserveShouldNotifyForRevokedCertificateUseCase +import com.wire.kalium.logic.feature.user.e2ei.ObserveShouldNotifyForRevokedCertificateUseCaseImpl import com.wire.kalium.logic.feature.user.SyncContactsUseCase import com.wire.kalium.logic.feature.user.SyncContactsUseCaseImpl import com.wire.kalium.logic.feature.user.SyncSelfUserUseCase @@ -304,6 +306,8 @@ import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsAndResolveOneO import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsUseCase import com.wire.kalium.logic.feature.user.UpdateSupportedProtocolsUseCaseImpl import com.wire.kalium.logic.feature.user.UserScope +import com.wire.kalium.logic.feature.user.e2ei.MarkNotifyForRevokedCertificateAsNotifiedUseCase +import com.wire.kalium.logic.feature.user.e2ei.MarkNotifyForRevokedCertificateAsNotifiedUseCaseImpl import com.wire.kalium.logic.feature.user.guestroomlink.MarkGuestLinkFeatureFlagAsNotChangedUseCase import com.wire.kalium.logic.feature.user.guestroomlink.MarkGuestLinkFeatureFlagAsNotChangedUseCaseImpl import com.wire.kalium.logic.feature.user.guestroomlink.ObserveGuestRoomLinkFeatureFlagUseCase @@ -1674,6 +1678,7 @@ class UserSessionScope internal constructor( val users: UserScope get() = UserScope( userRepository, + userConfigRepository, accountRepository, searchUserRepository, syncManager, @@ -1737,6 +1742,12 @@ class UserSessionScope internal constructor( val observeFileSharingStatus: ObserveFileSharingStatusUseCase get() = ObserveFileSharingStatusUseCaseImpl(userConfigRepository) + val observeShouldNotifyForRevokedCertificate: ObserveShouldNotifyForRevokedCertificateUseCase + by lazy { ObserveShouldNotifyForRevokedCertificateUseCaseImpl(userConfigRepository) } + + val markNotifyForRevokedCertificateAsNotified: MarkNotifyForRevokedCertificateAsNotifiedUseCase + by lazy { MarkNotifyForRevokedCertificateAsNotifiedUseCaseImpl(userConfigRepository) } + val markGuestLinkFeatureFlagAsNotChanged: MarkGuestLinkFeatureFlagAsNotChangedUseCase get() = MarkGuestLinkFeatureFlagAsNotChangedUseCaseImpl(userConfigRepository) @@ -1922,6 +1933,9 @@ class UserSessionScope internal constructor( launch { updateSelfClientCapabilityToLegalHoldConsent() } + launch { + users.observeCertificateRevocationForSelfClient() + } } fun onDestroy() { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/ObserveCertificateRevocationForSelfClientUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/ObserveCertificateRevocationForSelfClientUseCase.kt new file mode 100644 index 00000000000..0dd332e36eb --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/usecase/ObserveCertificateRevocationForSelfClientUseCase.kt @@ -0,0 +1,47 @@ +/* + * Wire + * Copyright (C) 2024 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.e2ei.usecase + +import com.wire.kalium.logic.configuration.UserConfigRepository +import com.wire.kalium.logic.data.id.CurrentClientIdProvider +import com.wire.kalium.logic.feature.e2ei.CertificateStatus +import com.wire.kalium.logic.functional.map + +/** + * Use case to observe certificate revocation for self client. + */ +interface ObserveCertificateRevocationForSelfClientUseCase { + suspend operator fun invoke() +} + +@Suppress("LongParameterList") +internal class ObserveCertificateRevocationForSelfClientUseCaseImpl( + private val userConfigRepository: UserConfigRepository, + private val currentClientIdProvider: CurrentClientIdProvider, + private val getE2eiCertificate: GetE2eiCertificateUseCase +) : ObserveCertificateRevocationForSelfClientUseCase { + override suspend fun invoke() { + currentClientIdProvider().map { clientId -> + getE2eiCertificate(clientId).run { + if (this is GetE2EICertificateUseCaseResult.Success && certificate.status == CertificateStatus.REVOKED) { + userConfigRepository.setShouldNotifyForRevokedCertificate(true) + } + } + } + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt index b1386a6bc65..08a5ecfc656 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt @@ -19,6 +19,7 @@ package com.wire.kalium.logic.feature.user +import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.configuration.server.ServerConfigRepository import com.wire.kalium.logic.data.asset.AssetRepository import com.wire.kalium.logic.data.connection.ConnectionRepository @@ -52,8 +53,13 @@ import com.wire.kalium.logic.feature.e2ei.usecase.GetMembersE2EICertificateStatu import com.wire.kalium.logic.feature.e2ei.usecase.GetMembersE2EICertificateStatusesUseCaseImpl import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificatesUseCase import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificatesUseCaseImpl +<<<<<<< HEAD import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusUseCase import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusUseCaseImpl +======= +import com.wire.kalium.logic.feature.e2ei.usecase.ObserveCertificateRevocationForSelfClientUseCase +import com.wire.kalium.logic.feature.e2ei.usecase.ObserveCertificateRevocationForSelfClientUseCaseImpl +>>>>>>> 35d3229ed7 (feat: Observe self client certificate revocation (WPB-6145) (#2384)) import com.wire.kalium.logic.feature.message.MessageSender import com.wire.kalium.logic.feature.publicuser.GetAllContactsUseCase import com.wire.kalium.logic.feature.publicuser.GetAllContactsUseCaseImpl @@ -75,6 +81,7 @@ import com.wire.kalium.persistence.dao.MetadataDAO @Suppress("LongParameterList") class UserScope internal constructor( private val userRepository: UserRepository, + private val userConfigRepository: UserConfigRepository, private val accountRepository: AccountRepository, private val searchUserRepository: SearchUserRepository, private val syncManager: SyncManager, @@ -176,4 +183,11 @@ class UserScope internal constructor( val deleteAccount: DeleteAccountUseCase get() = DeleteAccountUseCase(accountRepository) val updateSupportedProtocols: UpdateSupportedProtocolsUseCase get() = updateSupportedProtocolsUseCase + + val observeCertificateRevocationForSelfClient: ObserveCertificateRevocationForSelfClientUseCase + get() = ObserveCertificateRevocationForSelfClientUseCaseImpl( + userConfigRepository = userConfigRepository, + currentClientIdProvider = clientIdProvider, + getE2eiCertificate = getE2EICertificate + ) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/e2ei/MarkNotifyForRevokedCertificateAsNotifiedUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/e2ei/MarkNotifyForRevokedCertificateAsNotifiedUseCase.kt new file mode 100644 index 00000000000..50058481030 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/e2ei/MarkNotifyForRevokedCertificateAsNotifiedUseCase.kt @@ -0,0 +1,35 @@ +/* + * Wire + * Copyright (C) 2024 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.user.e2ei + +import com.wire.kalium.logic.configuration.UserConfigRepository + +/** + * Use case that marks that the user should not be notified about revoked E2Ei certificate. + */ +interface MarkNotifyForRevokedCertificateAsNotifiedUseCase { + suspend operator fun invoke() +} + +internal class MarkNotifyForRevokedCertificateAsNotifiedUseCaseImpl( + private val userConfigRepository: UserConfigRepository +) : MarkNotifyForRevokedCertificateAsNotifiedUseCase { + override suspend operator fun invoke() { + userConfigRepository.setShouldNotifyForRevokedCertificate(false) + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/e2ei/ObserveShouldNotifyForRevokedCertificateUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/e2ei/ObserveShouldNotifyForRevokedCertificateUseCase.kt new file mode 100644 index 00000000000..f9af3cb824a --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/e2ei/ObserveShouldNotifyForRevokedCertificateUseCase.kt @@ -0,0 +1,47 @@ +/* + * Wire + * Copyright (C) 2024 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.user.e2ei + +import com.wire.kalium.logic.configuration.UserConfigRepository +import com.wire.kalium.logic.functional.fold +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf + +/** + * Use case that observes if the user should be notified about revoked E2ei certificate. + */ +interface ObserveShouldNotifyForRevokedCertificateUseCase { + suspend operator fun invoke(): Flow +} + +internal class ObserveShouldNotifyForRevokedCertificateUseCaseImpl( + private val userConfigRepository: UserConfigRepository +) : ObserveShouldNotifyForRevokedCertificateUseCase { + @OptIn(ExperimentalCoroutinesApi::class) + override suspend operator fun invoke(): Flow = + userConfigRepository.observeShouldNotifyForRevokedCertificate().flatMapLatest { + it.fold( + { flowOf(false) }, + { shouldNotify -> + flowOf(shouldNotify) + } + ) + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/e2ei/MarkNotifyForRevokedCertificateAsNotifiedUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/e2ei/MarkNotifyForRevokedCertificateAsNotifiedUseCaseTest.kt new file mode 100644 index 00000000000..44bfd2e2023 --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/e2ei/MarkNotifyForRevokedCertificateAsNotifiedUseCaseTest.kt @@ -0,0 +1,63 @@ +/* + * Wire + * Copyright (C) 2024 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.user.e2ei + +import com.wire.kalium.logic.configuration.UserConfigRepository +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.eq +import io.mockative.given +import io.mockative.mock +import io.mockative.verify +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +class MarkNotifyForRevokedCertificateAsNotifiedUseCaseTest { + + @Test + fun givenUserConfigRepository_whenRunningUseCase_thenSetShouldNotifyForRevokedCertificateOnce() = + runTest { + val (arrangement, markNotifyForRevokedCertificateAsNotified) = Arrangement() + .withUserConfigRepository() + .arrange() + + markNotifyForRevokedCertificateAsNotified.invoke() + + verify(arrangement.userConfigRepository) + .function(arrangement.userConfigRepository::setShouldNotifyForRevokedCertificate) + .with(eq(false)) + .wasInvoked() + } + + internal class Arrangement { + + @Mock + val userConfigRepository = mock(classOf()) + + fun arrange() = this to MarkNotifyForRevokedCertificateAsNotifiedUseCaseImpl( + userConfigRepository = userConfigRepository + ) + + fun withUserConfigRepository() = apply { + given(userConfigRepository) + .function(userConfigRepository::setShouldNotifyForRevokedCertificate) + .whenInvokedWith(eq(false)) + .thenReturn(Unit) + } + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/e2ei/ObserveShouldNotifyForRevokedCertificateUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/e2ei/ObserveShouldNotifyForRevokedCertificateUseCaseTest.kt new file mode 100644 index 00000000000..a7daf079a5e --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/e2ei/ObserveShouldNotifyForRevokedCertificateUseCaseTest.kt @@ -0,0 +1,81 @@ +/* + * Wire + * Copyright (C) 2024 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.user.e2ei + +import com.wire.kalium.logic.StorageFailure +import com.wire.kalium.logic.configuration.UserConfigRepository +import com.wire.kalium.logic.functional.Either +import io.mockative.Mock +import io.mockative.classOf +import io.mockative.given +import io.mockative.mock +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class ObserveShouldNotifyForRevokedCertificateUseCaseTest { + + @Test + fun givenUserConfigRepositoryFailure_whenRunningUseCase_thenEmitFalse() = runTest { + val (_, observeShouldNotifyForRevokedCertificate) = Arrangement() + .withUserConfigRepositoryFailure() + .arrange() + + val result = observeShouldNotifyForRevokedCertificate.invoke() + + assertEquals(false, result.first()) + } + + @Test + fun givenUserConfigRepositorySuccess_whenRunningUseCase_thenEmitSameValueOfRepository() = + runTest { + val (_, observeShouldNotifyForRevokedCertificate) = Arrangement() + .withUserConfigRepositorySuccess() + .arrange() + + val result = observeShouldNotifyForRevokedCertificate.invoke() + + assertEquals(true, result.first()) + } + + internal class Arrangement { + + @Mock + val userConfigRepository = mock(classOf()) + + fun arrange() = this to ObserveShouldNotifyForRevokedCertificateUseCaseImpl( + userConfigRepository = userConfigRepository + ) + + fun withUserConfigRepositoryFailure() = apply { + given(userConfigRepository) + .suspendFunction(userConfigRepository::observeShouldNotifyForRevokedCertificate) + .whenInvoked() + .thenReturn(flowOf(Either.Left(StorageFailure.DataNotFound))) + } + + fun withUserConfigRepositorySuccess() = apply { + given(userConfigRepository) + .suspendFunction(userConfigRepository::observeShouldNotifyForRevokedCertificate) + .whenInvoked() + .thenReturn(flowOf(Either.Right(true))) + } + } +} diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/unread/UserConfigDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/unread/UserConfigDAO.kt index 3776fd6c279..e7a9e1f3509 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/unread/UserConfigDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/unread/UserConfigDAO.kt @@ -50,6 +50,14 @@ interface UserConfigDAO { suspend fun observeLegalHoldChangeNotified(): Flow suspend fun setShouldUpdateClientLegalHoldCapability(shouldUpdate: Boolean) suspend fun shouldUpdateClientLegalHoldCapability(): Boolean +<<<<<<< HEAD +======= + suspend fun setCRLExpirationTime(url: String, timestamp: ULong) + suspend fun getCRLsPerDomain(url: String): ULong? + suspend fun observeCertificateExpirationTime(url: String): Flow + suspend fun setShouldNotifyForRevokedCertificate(shouldNotify: Boolean) + suspend fun observeShouldNotifyForRevokedCertificate(): Flow +>>>>>>> 35d3229ed7 (feat: Observe self client certificate revocation (WPB-6145) (#2384)) } @Suppress("TooManyFunctions") @@ -135,8 +143,32 @@ internal class UserConfigDAOImpl internal constructor( override suspend fun shouldUpdateClientLegalHoldCapability(): Boolean = metadataDAO.valueByKey(SHOULD_UPDATE_CLIENT_LEGAL_HOLD_CAPABILITY)?.toBoolean() ?: true +<<<<<<< HEAD +======= + override suspend fun setCRLExpirationTime(url: String, timestamp: ULong) { + metadataDAO.insertValue( + key = url, + value = timestamp.toString() + ) + } + + override suspend fun getCRLsPerDomain(url: String): ULong? = + metadataDAO.valueByKey(url)?.toULongOrNull() + + override suspend fun observeCertificateExpirationTime(url: String): Flow = + metadataDAO.valueByKeyFlow(url).map { it?.toULongOrNull() } + + override suspend fun setShouldNotifyForRevokedCertificate(shouldNotify: Boolean) { + metadataDAO.insertValue(shouldNotify.toString(), SHOULD_NOTIFY_FOR_REVOKED_CERTIFICATE) + } + + override suspend fun observeShouldNotifyForRevokedCertificate(): Flow = + metadataDAO.valueByKeyFlow(SHOULD_NOTIFY_FOR_REVOKED_CERTIFICATE).map { it?.toBoolean() } + +>>>>>>> 35d3229ed7 (feat: Observe self client certificate revocation (WPB-6145) (#2384)) private companion object { private const val SELF_DELETING_MESSAGES_KEY = "SELF_DELETING_MESSAGES" + private const val SHOULD_NOTIFY_FOR_REVOKED_CERTIFICATE = "should_notify_for_revoked_certificate" private const val MLS_MIGRATION_KEY = "MLS_MIGRATION" private const val SUPPORTED_PROTOCOLS_KEY = "SUPPORTED_PROTOCOLS" const val LEGAL_HOLD_REQUEST = "legal_hold_request"