Skip to content

Commit

Permalink
feat: detect team app lock change (WPB-4476) (#2183)
Browse files Browse the repository at this point in the history
* feat: use case for observing team app lock config

* chore: rename Applock data class

* feat: detect team app lock change

* fix: app lock feature dialog being displayed when timeout is changed from team admin

* chore: detekt

* chore: unit test

* chore: consider value changed when it's not found locally and config is enabled

* chore: cleanup

* chore: make use case internal
  • Loading branch information
ohassine authored Nov 8, 2023
1 parent 323edb6 commit 603c0a3
Show file tree
Hide file tree
Showing 18 changed files with 560 additions and 93 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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.configuration

import kotlin.time.Duration

data class AppLockTeamConfig(
val isEnabled: Boolean,
val timeout: Duration,
val isStatusChanged: Boolean?
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
package com.wire.kalium.logic.configuration

import com.wire.kalium.logic.StorageFailure
import com.wire.kalium.logic.data.featureConfig.AppLockConfigModel
import com.wire.kalium.logic.data.featureConfig.MLSMigrationModel
import com.wire.kalium.logic.data.featureConfig.toEntity
import com.wire.kalium.logic.data.featureConfig.toModel
Expand All @@ -46,16 +45,32 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.datetime.Instant
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

@Suppress("TooManyFunctions")
interface UserConfigRepository {
fun setAppLockStatus(status: AppLockConfigModel): Either<StorageFailure, Unit>
fun observeAppLockStatus(): Flow<Either<StorageFailure, AppLockConfigModel>>
fun setFileSharingStatus(status: Boolean, isStatusChanged: Boolean?): Either<StorageFailure, Unit>
fun setAppLockStatus(
isAppLocked: Boolean,
timeout: Int,
isStatusChanged: Boolean?
): Either<StorageFailure, Unit>

fun isTeamAppLockEnabled(): Either<StorageFailure, AppLockTeamConfig>
fun observeAppLockConfig(): Flow<Either<StorageFailure, AppLockTeamConfig>>
fun setTeamAppLockAsNotified(): Either<StorageFailure, Unit>
fun setFileSharingStatus(
status: Boolean,
isStatusChanged: Boolean?
): Either<StorageFailure, Unit>

fun setFileSharingAsNotified(): Either<StorageFailure, Unit>
fun isFileSharingEnabled(): Either<StorageFailure, FileSharingStatus>
fun isFileSharingEnabledFlow(): Flow<Either<StorageFailure, FileSharingStatus>>
fun setClassifiedDomainsStatus(enabled: Boolean, domains: List<String>): Either<StorageFailure, Unit>
fun setClassifiedDomainsStatus(
enabled: Boolean,
domains: List<String>
): Either<StorageFailure, Unit>

fun getClassifiedDomainsStatus(): Flow<Either<StorageFailure, ClassifiedDomainsStatus>>
fun isMLSEnabled(): Either<StorageFailure, Boolean>
fun setMLSEnabled(enabled: Boolean): Either<StorageFailure, Unit>
Expand Down Expand Up @@ -101,7 +116,10 @@ class UserConfigDataSource(
private val kaliumConfigs: KaliumConfigs
) : UserConfigRepository {

override fun setFileSharingStatus(status: Boolean, isStatusChanged: Boolean?): Either<StorageFailure, Unit> =
override fun setFileSharingStatus(
status: Boolean,
isStatusChanged: Boolean?
): Either<StorageFailure, Unit> =
wrapStorageRequest { userConfigStorage.persistFileSharingStatus(status, isStatusChanged) }

override fun setFileSharingAsNotified(): Either<StorageFailure, Unit> = wrapStorageRequest {
Expand Down Expand Up @@ -196,7 +214,8 @@ class UserConfigDataSource(
}
}

private fun getE2EINotificationTimeOrNull() = wrapStorageRequest { userConfigStorage.getE2EINotificationTime() }.getOrNull()
private fun getE2EINotificationTimeOrNull() =
wrapStorageRequest { userConfigStorage.getE2EINotificationTime() }.getOrNull()

override fun setDefaultProtocol(protocol: SupportedProtocol): Either<StorageFailure, Unit> =
wrapStorageRequest { userConfigStorage.persistDefaultProtocol(protocol.toDao()) }
Expand All @@ -209,6 +228,7 @@ class UserConfigDataSource(

override suspend fun getSupportedProtocols(): Either<StorageFailure, Set<SupportedProtocol>> =
wrapStorageRequest { userConfigDAO.getSupportedProtocols()?.toModel() }

override fun setConferenceCallingEnabled(enabled: Boolean): Either<StorageFailure, Unit> =
wrapStorageRequest {
userConfigStorage.persistConferenceCalling(enabled)
Expand All @@ -224,9 +244,10 @@ class UserConfigDataSource(
userConfigStorage.persistSecondFactorPasswordChallengeStatus(isRequired)
}

override fun isSecondFactorPasswordChallengeRequired(): Either<StorageFailure, Boolean> = wrapStorageRequest {
userConfigStorage.isSecondFactorPasswordChallengeRequired()
}
override fun isSecondFactorPasswordChallengeRequired(): Either<StorageFailure, Boolean> =
wrapStorageRequest {
userConfigStorage.isSecondFactorPasswordChallengeRequired()
}

override fun isReadReceiptsEnabled(): Flow<Either<StorageFailure, Boolean>> =
userConfigStorage.areReadReceiptsEnabled().wrapStorageRequest()
Expand All @@ -239,11 +260,15 @@ class UserConfigDataSource(
override fun isTypingIndicatorEnabled(): Flow<Either<StorageFailure, Boolean>> =
userConfigStorage.isTypingIndicatorEnabled().wrapStorageRequest()

override fun setTypingIndicatorStatus(enabled: Boolean): Either<StorageFailure, Unit> = wrapStorageRequest {
userConfigStorage.persistTypingIndicator(enabled)
}
override fun setTypingIndicatorStatus(enabled: Boolean): Either<StorageFailure, Unit> =
wrapStorageRequest {
userConfigStorage.persistTypingIndicator(enabled)
}

override fun setGuestRoomStatus(status: Boolean, isStatusChanged: Boolean?): Either<StorageFailure, Unit> =
override fun setGuestRoomStatus(
status: Boolean,
isStatusChanged: Boolean?
): Either<StorageFailure, Unit> =
wrapStorageRequest {
userConfigStorage.persistGuestRoomLinkFeatureFlag(status, isStatusChanged)
}
Expand All @@ -258,20 +283,24 @@ class UserConfigDataSource(
.wrapStorageRequest()
.map {
it.map { isGuestRoomLinkEnabledEntity ->
GuestRoomLinkStatus(isGuestRoomLinkEnabledEntity.status, isGuestRoomLinkEnabledEntity.isStatusChanged)
GuestRoomLinkStatus(
isGuestRoomLinkEnabledEntity.status,
isGuestRoomLinkEnabledEntity.isStatusChanged
)
}
}

override suspend fun getTeamSettingsSelfDeletionStatus(): Either<StorageFailure, TeamSettingsSelfDeletionStatus> = wrapStorageRequest {
userConfigDAO.getTeamSettingsSelfDeletionStatus()
}.map {
with(it) {
TeamSettingsSelfDeletionStatus(
hasFeatureChanged = isStatusChanged,
enforcedSelfDeletionTimer = selfDeletionTimerEntity.toTeamSelfDeleteTimer()
)
override suspend fun getTeamSettingsSelfDeletionStatus(): Either<StorageFailure, TeamSettingsSelfDeletionStatus> =
wrapStorageRequest {
userConfigDAO.getTeamSettingsSelfDeletionStatus()
}.map {
with(it) {
TeamSettingsSelfDeletionStatus(
hasFeatureChanged = isStatusChanged,
enforcedSelfDeletionTimer = selfDeletionTimerEntity.toTeamSelfDeleteTimer()
)
}
}
}

override suspend fun setTeamSettingsSelfDeletionStatus(teamSettingsSelfDeletionStatus: TeamSettingsSelfDeletionStatus):
Either<StorageFailure, Unit> =
Expand All @@ -283,9 +312,10 @@ class UserConfigDataSource(
userConfigDAO.setTeamSettingsSelfDeletionStatus(teamSettingsSelfDeletionStatusEntity)
}

override suspend fun markTeamSettingsSelfDeletingMessagesStatusAsNotified(): Either<StorageFailure, Unit> = wrapStorageRequest {
userConfigDAO.markTeamSettingsSelfDeletingMessagesStatusAsNotified()
}
override suspend fun markTeamSettingsSelfDeletingMessagesStatusAsNotified(): Either<StorageFailure, Unit> =
wrapStorageRequest {
userConfigDAO.markTeamSettingsSelfDeletingMessagesStatusAsNotified()
}

override suspend fun observeTeamSettingsSelfDeletingStatus(): Flow<Either<StorageFailure, TeamSettingsSelfDeletionStatus>> =
userConfigDAO.observeTeamSettingsSelfDeletingStatus().wrapStorageRequest().map {
Expand All @@ -303,26 +333,47 @@ class UserConfigDataSource(
override suspend fun observeScreenshotCensoringConfig(): Flow<Either<StorageFailure, Boolean>> =
userConfigStorage.isScreenshotCensoringEnabledFlow().wrapStorageRequest()

override fun setAppLockStatus(status: AppLockConfigModel): Either<StorageFailure, Unit> =
override fun setAppLockStatus(
isAppLocked: Boolean,
timeout: Int,
isStatusChanged: Boolean?
): Either<StorageFailure, Unit> =
wrapStorageRequest {
userConfigStorage.persistAppLockStatus(
status.enforceAppLock,
status.inactivityTimeoutSecs
isAppLocked,
timeout,
isStatusChanged
)
}

override fun observeAppLockStatus(): Flow<Either<StorageFailure, AppLockConfigModel>> =
override fun observeAppLockConfig(): Flow<Either<StorageFailure, AppLockTeamConfig>> =
wrapFlowStorageRequest {
userConfigStorage.appLockFlow().map {
it?.let { config ->
AppLockConfigModel(
config.enforceAppLock,
config.inactivityTimeoutSecs
AppLockTeamConfig(
isEnabled = config.enforceAppLock,
timeout = config.inactivityTimeoutSecs.seconds,
isStatusChanged = config.isStatusChanged
)
}
}
}

override fun isTeamAppLockEnabled(): Either<StorageFailure, AppLockTeamConfig> {
val serverSideConfig = wrapStorageRequest { userConfigStorage.appLockStatus() }
return serverSideConfig.map {
AppLockTeamConfig(
isEnabled = it.enforceAppLock,
timeout = it.inactivityTimeoutSecs.seconds,
isStatusChanged = it.isStatusChanged
)
}
}

override fun setTeamAppLockAsNotified(): Either<StorageFailure, Unit> = wrapStorageRequest {
userConfigStorage.setTeamAppLockAsNotified()
}

override suspend fun getMigrationConfiguration(): Either<StorageFailure, MLSMigrationModel> =
wrapStorageRequest {
userConfigDAO.getMigrationConfiguration()?.toModel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ sealed class Event(open val id: String, open val transient: Boolean, open val li
typeKey to "FeatureConfig.AppLockUpdated",
idKey to id.obfuscateId(),
featureStatusKey to model.status.name,
"config" to model.config
"timeout" to model.inactivityTimeoutSecs
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ class FeatureConfigMapperImpl : FeatureConfigMapper {

override fun fromDTO(data: FeatureConfigData.AppLock): AppLockModel =
AppLockModel(
AppLockConfigModel(data.config.enforceAppLock, data.config.inactivityTimeoutSecs),
fromDTO(data.status)
status = if (data.config.enforceAppLock) Status.ENABLED else Status.DISABLED,
inactivityTimeoutSecs = data.config.inactivityTimeoutSecs
)

override fun fromDTO(data: FeatureConfigData.ClassifiedDomains): ClassifiedDomainsModel =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,13 @@ data class FeatureConfigModel(

enum class Status {
ENABLED,
DISABLED
DISABLED;

fun toBoolean(): Boolean = this == ENABLED
}

data class AppLockModel(
val config: AppLockConfigModel,
val status: Status
)

data class AppLockConfigModel(
val enforceAppLock: Boolean,
val status: Status,
val inactivityTimeoutSecs: Second
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ import com.wire.kalium.logic.di.RootPathsProvider
import com.wire.kalium.logic.di.UserStorageProvider
import com.wire.kalium.logic.feature.applock.AppLockTeamFeatureConfigObserver
import com.wire.kalium.logic.feature.applock.AppLockTeamFeatureConfigObserverImpl
import com.wire.kalium.logic.feature.applock.MarkTeamAppLockStatusAsNotifiedUseCase
import com.wire.kalium.logic.feature.applock.MarkTeamAppLockStatusAsNotifiedUseCaseImpl
import com.wire.kalium.logic.feature.asset.ValidateAssetMimeTypeUseCase
import com.wire.kalium.logic.feature.asset.ValidateAssetMimeTypeUseCaseImpl
import com.wire.kalium.logic.feature.auth.AuthenticationScope
Expand Down Expand Up @@ -1586,7 +1588,10 @@ class UserSessionScope internal constructor(
get() = MarkGuestLinkFeatureFlagAsNotChangedUseCaseImpl(userConfigRepository)

val appLockTeamFeatureConfigObserver: AppLockTeamFeatureConfigObserver
get() = AppLockTeamFeatureConfigObserverImpl(userConfigRepository)
get() = AppLockTeamFeatureConfigObserverImpl(userConfigRepository, kaliumConfigs)

val markTeamAppLockStatusAsNotified: MarkTeamAppLockStatusAsNotifiedUseCase
get() = MarkTeamAppLockStatusAsNotifiedUseCaseImpl(userConfigRepository)

val markSelfDeletingMessagesAsNotified: MarkSelfDeletionStatusAsNotifiedUseCase
get() = MarkSelfDeletionStatusAsNotifiedUseCaseImpl(userConfigRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
*/
package com.wire.kalium.logic.feature.applock

import com.wire.kalium.logic.configuration.AppLockTeamConfig
import com.wire.kalium.logic.configuration.UserConfigRepository
import com.wire.kalium.logic.featureFlags.KaliumConfigs
import com.wire.kalium.logic.functional.fold
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlin.time.Duration.Companion.seconds

Expand All @@ -31,22 +34,31 @@ interface AppLockTeamFeatureConfigObserver {
}

class AppLockTeamFeatureConfigObserverImpl(
private val userConfigRepository: UserConfigRepository
private val userConfigRepository: UserConfigRepository,
private val kaliumConfigs: KaliumConfigs
) : AppLockTeamFeatureConfigObserver {
override fun invoke(): Flow<AppLockTeamConfig> =
userConfigRepository.observeAppLockStatus().map {
it.fold({
override fun invoke(): Flow<AppLockTeamConfig> {
if (kaliumConfigs.teamAppLock) {
return flowOf(
AppLockTeamConfig(
isEnabled = false,
timeout = DEFAULT_TIMEOUT
isEnabled = true,
timeout = kaliumConfigs.teamAppLockTimeout.seconds,
isStatusChanged = false
)
}, { appLockModel ->
)
}
return userConfigRepository.observeAppLockConfig().map {
it.fold({
AppLockTeamConfig(
isEnabled = appLockModel.enforceAppLock,
timeout = appLockModel.inactivityTimeoutSecs.seconds
isEnabled = false,
timeout = DEFAULT_TIMEOUT,
isStatusChanged = false
)
}, { appLock ->
appLock
})
}
}

companion object {
val DEFAULT_TIMEOUT = 60.seconds
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.applock

import com.wire.kalium.logic.configuration.UserConfigRepository

/**
* Mark the team app lock status as notified
* need to be called after notifying the user about the change
* e.g. after showing a dialog, or a toast etc.
*/
interface MarkTeamAppLockStatusAsNotifiedUseCase {
operator fun invoke()
}

class MarkTeamAppLockStatusAsNotifiedUseCaseImpl internal constructor(
private val userConfigRepository: UserConfigRepository
) : MarkTeamAppLockStatusAsNotifiedUseCase {
override operator fun invoke() {
userConfigRepository.setTeamAppLockAsNotified()
}
}
Loading

0 comments on commit 603c0a3

Please sign in to comment.