Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: private users can remove app lock even after team enforce #2241

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -116,7 +116,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 @@ -357,24 +357,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 @@ -546,7 +546,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 @@ -1630,7 +1630,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
Loading