Skip to content

Commit

Permalink
fix: private users can remove app lock even after team enforce (#2240)
Browse files Browse the repository at this point in the history
* fix: app lock can be changes when enforced and it is cleared when not enforced

* add docs
  • Loading branch information
MohamadJaara authored Nov 21, 2023
1 parent 884b41a commit 611665a
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import com.wire.kalium.logic.feature.client.ClearNewClientsForUserUseCase
import com.wire.kalium.logic.feature.client.ClearNewClientsForUserUseCaseImpl
import com.wire.kalium.logic.feature.client.ObserveNewClientsUseCase
import com.wire.kalium.logic.feature.client.ObserveNewClientsUseCaseImpl
import com.wire.kalium.logic.feature.featureConfig.IsAppLockEditableUseCase
import com.wire.kalium.logic.feature.notificationToken.SaveNotificationTokenUseCase
import com.wire.kalium.logic.feature.notificationToken.SaveNotificationTokenUseCaseImpl
import com.wire.kalium.logic.feature.rootDetection.CheckSystemIntegrityUseCase
Expand Down Expand Up @@ -186,4 +187,7 @@ class GlobalKaliumScope internal constructor(

val clearNewClientsForUser: ClearNewClientsForUserUseCase
get() = ClearNewClientsForUserUseCaseImpl(userSessionScopeProvider.value)

val isAppLockEditableUseCase: IsAppLockEditableUseCase
get() = IsAppLockEditableUseCase(userSessionScopeProvider.value, sessionRepository)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package com.wire.kalium.logic.configuration
import kotlin.time.Duration

data class AppLockTeamConfig(
val isEnabled: Boolean,
val isEnforced: Boolean,
val timeout: Duration,
val isStatusChanged: Boolean?
)
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ import com.wire.kalium.logic.StorageFailure
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.user.SupportedProtocol
import com.wire.kalium.logic.data.user.toDao
import com.wire.kalium.logic.data.user.toModel
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
import com.wire.kalium.logic.data.user.SupportedProtocol
import com.wire.kalium.logic.data.user.toDao
import com.wire.kalium.logic.data.user.toModel
import com.wire.kalium.logic.featureFlags.BuildFileRestrictionState
import com.wire.kalium.logic.featureFlags.KaliumConfigs
import com.wire.kalium.logic.functional.Either
Expand Down Expand Up @@ -110,7 +110,7 @@ interface UserConfigRepository {
}

@Suppress("TooManyFunctions")
class UserConfigDataSource(
internal class UserConfigDataSource internal constructor(
private val userConfigStorage: UserConfigStorage,
private val userConfigDAO: UserConfigDAO,
private val kaliumConfigs: KaliumConfigs
Expand Down Expand Up @@ -351,24 +351,24 @@ class UserConfigDataSource(
userConfigStorage.appLockFlow().map {
it?.let { config ->
AppLockTeamConfig(
isEnabled = config.enforceAppLock,
isEnforced = config.enforceAppLock,
timeout = config.inactivityTimeoutSecs.seconds,
isStatusChanged = config.isStatusChanged
)
}
}
}

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

override fun setTeamAppLockAsNotified(): Either<StorageFailure, Unit> = wrapStorageRequest {
userConfigStorage.setTeamAppLockAsNotified()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ package com.wire.kalium.logic.data.session
import com.wire.kalium.logic.StorageFailure
import com.wire.kalium.logic.configuration.server.ServerConfig
import com.wire.kalium.logic.configuration.server.ServerConfigRepository
import com.wire.kalium.logic.data.auth.Account
import com.wire.kalium.logic.data.auth.AccountInfo
import com.wire.kalium.logic.data.auth.AccountTokens
import com.wire.kalium.logic.data.auth.PersistentWebSocketStatus
import com.wire.kalium.logic.data.auth.login.ProxyCredentials
import com.wire.kalium.logic.data.id.IdMapper
import com.wire.kalium.logic.data.id.toDao
Expand All @@ -29,10 +33,6 @@ import com.wire.kalium.logic.data.logout.LogoutReason
import com.wire.kalium.logic.data.user.SsoId
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.di.MapperProvider
import com.wire.kalium.logic.data.auth.Account
import com.wire.kalium.logic.data.auth.AccountInfo
import com.wire.kalium.logic.data.auth.AccountTokens
import com.wire.kalium.logic.data.auth.PersistentWebSocketStatus
import com.wire.kalium.logic.featureFlags.KaliumConfigs
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.flatMap
Expand Down Expand Up @@ -81,7 +81,7 @@ interface SessionRepository {
}

@Suppress("TooManyFunctions")
internal class SessionDataSource(
internal class SessionDataSource internal constructor(
private val accountsDAO: AccountsDAO,
private val authTokenStorage: AuthTokenStorage,
private val serverConfigRepository: ServerConfigRepository,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ import com.wire.kalium.logic.data.conversation.ConversationDataSource
import com.wire.kalium.logic.data.conversation.ConversationGroupRepository
import com.wire.kalium.logic.data.conversation.ConversationGroupRepositoryImpl
import com.wire.kalium.logic.data.conversation.ConversationRepository
import com.wire.kalium.logic.data.conversation.JoinExistingMLSConversationUseCase
import com.wire.kalium.logic.data.conversation.JoinExistingMLSConversationUseCaseImpl
import com.wire.kalium.logic.data.conversation.JoinExistingMLSConversationsUseCase
import com.wire.kalium.logic.data.conversation.JoinExistingMLSConversationsUseCaseImpl
import com.wire.kalium.logic.data.conversation.JoinSubconversationUseCase
import com.wire.kalium.logic.data.conversation.JoinSubconversationUseCaseImpl
import com.wire.kalium.logic.data.conversation.LeaveSubconversationUseCase
import com.wire.kalium.logic.data.conversation.LeaveSubconversationUseCaseImpl
import com.wire.kalium.logic.data.conversation.MLSConversationDataSource
import com.wire.kalium.logic.data.conversation.MLSConversationRepository
import com.wire.kalium.logic.data.conversation.NewConversationMembersRepository
Expand Down Expand Up @@ -167,14 +175,6 @@ import com.wire.kalium.logic.feature.connection.SyncConnectionsUseCaseImpl
import com.wire.kalium.logic.feature.conversation.ConversationScope
import com.wire.kalium.logic.feature.conversation.ConversationsRecoveryManager
import com.wire.kalium.logic.feature.conversation.ConversationsRecoveryManagerImpl
import com.wire.kalium.logic.data.conversation.JoinExistingMLSConversationUseCase
import com.wire.kalium.logic.data.conversation.JoinExistingMLSConversationUseCaseImpl
import com.wire.kalium.logic.data.conversation.JoinExistingMLSConversationsUseCase
import com.wire.kalium.logic.data.conversation.JoinExistingMLSConversationsUseCaseImpl
import com.wire.kalium.logic.data.conversation.JoinSubconversationUseCase
import com.wire.kalium.logic.data.conversation.JoinSubconversationUseCaseImpl
import com.wire.kalium.logic.data.conversation.LeaveSubconversationUseCase
import com.wire.kalium.logic.data.conversation.LeaveSubconversationUseCaseImpl
import com.wire.kalium.logic.feature.conversation.MLSConversationsRecoveryManager
import com.wire.kalium.logic.feature.conversation.MLSConversationsRecoveryManagerImpl
import com.wire.kalium.logic.feature.conversation.MLSConversationsVerificationStatusesHandler
Expand Down Expand Up @@ -208,9 +208,9 @@ import com.wire.kalium.logic.feature.featureConfig.handler.E2EIConfigHandler
import com.wire.kalium.logic.feature.featureConfig.handler.FileSharingConfigHandler
import com.wire.kalium.logic.feature.featureConfig.handler.GuestRoomConfigHandler
import com.wire.kalium.logic.feature.featureConfig.handler.MLSConfigHandler
import com.wire.kalium.logic.feature.featureConfig.handler.MLSMigrationConfigHandler
import com.wire.kalium.logic.feature.featureConfig.handler.SecondFactorPasswordChallengeConfigHandler
import com.wire.kalium.logic.feature.featureConfig.handler.SelfDeletingMessagesConfigHandler
import com.wire.kalium.logic.feature.featureConfig.handler.MLSMigrationConfigHandler
import com.wire.kalium.logic.feature.keypackage.KeyPackageManager
import com.wire.kalium.logic.feature.keypackage.KeyPackageManagerImpl
import com.wire.kalium.logic.feature.message.AddSystemMessageToAllConversationsUseCase
Expand Down Expand Up @@ -540,7 +540,7 @@ class UserSessionScope internal constructor(
mockEngine = kaliumConfigs.kaliumMockEngine?.mockEngine
)

private val userConfigRepository: UserConfigRepository
internal val userConfigRepository: UserConfigRepository
get() = UserConfigDataSource(
userStorage.preferences.userConfigStorage,
userStorage.database.userConfigDAO,
Expand Down Expand Up @@ -1598,7 +1598,7 @@ class UserSessionScope internal constructor(
get() = MarkGuestLinkFeatureFlagAsNotChangedUseCaseImpl(userConfigRepository)

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

val markTeamAppLockStatusAsNotified: MarkTeamAppLockStatusAsNotifiedUseCase
get() = MarkTeamAppLockStatusAsNotifiedUseCaseImpl(userConfigRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,22 @@ 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 com.wire.kalium.logic.functional.nullableFold
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlin.time.Duration.Companion.seconds

/**
* observe app lock feature flag of the team
*/
interface AppLockTeamFeatureConfigObserver {
operator fun invoke(): Flow<AppLockTeamConfig>
operator fun invoke(): Flow<AppLockTeamConfig?>
}

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

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

import com.wire.kalium.logic.data.session.SessionRepository
import com.wire.kalium.logic.feature.UserSessionScopeProvider
import com.wire.kalium.logic.functional.fold

/**
* Checks if the app lock is editable.
* The app lock is editable if there is no enforced app lock on any of the user's accounts.
* If there is an enforced app lock on any of the user's accounts, the app lock is not editable.
*/
class IsAppLockEditableUseCase internal constructor(
private val userSessionScopeProvider: UserSessionScopeProvider,
private val sessionRepository: SessionRepository
) {
suspend operator fun invoke(): Boolean =
sessionRepository.allValidSessions().fold({
true
}) { accounts ->
accounts.map { session ->
userSessionScopeProvider.getOrCreate(session.userId).let { userSessionScope ->
userSessionScope.userConfigRepository.isTeamAppLockEnabled().fold({
true
}, { appLockConfig ->
appLockConfig.isEnforced
})
}
}.contains(true).not()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import com.wire.kalium.logic.data.featureConfig.Status
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.nullableFold

class AppLockConfigHandler(
internal class AppLockConfigHandler internal constructor(
private val userConfigRepository: UserConfigRepository
) {
fun handle(appLockConfig: AppLockModel): Either<CoreFailure, Unit> {
Expand All @@ -36,7 +36,7 @@ class AppLockConfigHandler(
},
{
val newStatus = appLockConfig.status == Status.ENABLED
if (it.isEnabled != newStatus) true
if (it.isEnforced != newStatus) true
else it.isStatusChanged
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import com.wire.kalium.logic.data.featureConfig.ClassifiedDomainsModel
import com.wire.kalium.logic.data.featureConfig.Status
import com.wire.kalium.logic.functional.Either

class ClassifiedDomainsConfigHandler(
internal class ClassifiedDomainsConfigHandler internal constructor(
private val userConfigRepository: UserConfigRepository
) {
fun handle(classifiedDomainsConfig: ClassifiedDomainsModel): Either<CoreFailure, Unit> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@

package com.wire.kalium.logic.featureFlags

import com.wire.kalium.logic.feature.applock.AppLockTeamFeatureConfigObserverImpl.Companion.DEFAULT_TIMEOUT
import com.wire.kalium.logic.util.KaliumMockEngine
import com.wire.kalium.network.NetworkStateObserver
import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.DurationUnit

data class KaliumConfigs(
val forceConstantBitrateCalls: Boolean = false,
Expand All @@ -46,9 +44,7 @@ data class KaliumConfigs(
val kaliumMockEngine: KaliumMockEngine? = null,
val mockNetworkStateObserver: NetworkStateObserver? = null,
// Interval between attempts to advance the proteus to MLS migration
val mlsMigrationInterval: Duration = 24.hours,
val teamAppLock: Boolean = false,
val teamAppLockTimeout: Int = DEFAULT_TIMEOUT.toInt(DurationUnit.SECONDS),
val mlsMigrationInterval: Duration = 24.hours
)

sealed interface BuildFileRestrictionState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,37 +35,15 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.time.Duration.Companion.seconds

class AppLockTeamFeatureConfigObserverTest {

@Test
fun givenAppLockedByKaliumConfig_whenObservingAppLock_thenReturnAppLockWithStatusEnabled() =
fun givenRepositoryFailure_whenObservingAppLock_thenEmitNull() =
runTest {
val expectedAppLockValue = AppLockTeamConfig(
true,
50.seconds,
false
)
val (arrangement, observer) = Arrangement()
.arrangeWithCustomKaliumConfig()

val result = observer.invoke()

verify(arrangement.userConfigRepository)
.function(arrangement.userConfigRepository::observeAppLockConfig)
.wasNotInvoked()
assertEquals(expectedAppLockValue, result.first())
}

@Test
fun givenRepositoryFailure_whenObservingAppLock_thenEmitAppLockConfigWithDisabledStatus() =
runTest {
val expectedAppLockValue = AppLockTeamConfig(
false,
AppLockTeamFeatureConfigObserverImpl.DEFAULT_TIMEOUT,
false
)
val (arrangement, observer) = Arrangement()
.withFailure()
.arrange()
Expand All @@ -75,7 +53,7 @@ class AppLockTeamFeatureConfigObserverTest {
verify(arrangement.userConfigRepository)
.function(arrangement.userConfigRepository::observeAppLockConfig)
.wasInvoked(exactly = once)
assertEquals(expectedAppLockValue, result.first())
assertNull(result.first())
}

@Test
Expand Down Expand Up @@ -104,8 +82,6 @@ class AppLockTeamFeatureConfigObserverTest {
@Mock
val userConfigRepository = mock(classOf<UserConfigRepository>())

val kaliumConfigs = KaliumConfigs()

fun withFailure(): Arrangement = apply {
given(userConfigRepository)
.function(userConfigRepository::observeAppLockConfig)
Expand All @@ -121,13 +97,7 @@ class AppLockTeamFeatureConfigObserverTest {
}

fun arrange() = this to AppLockTeamFeatureConfigObserverImpl(
userConfigRepository = userConfigRepository,
kaliumConfigs = kaliumConfigs
)

fun arrangeWithCustomKaliumConfig() = this to AppLockTeamFeatureConfigObserverImpl(
userConfigRepository = userConfigRepository,
kaliumConfigs = KaliumConfigs(teamAppLock = true, teamAppLockTimeout = 50)
userConfigRepository = userConfigRepository
)
}

Expand Down

0 comments on commit 611665a

Please sign in to comment.