Skip to content

Commit

Permalink
fix: app lock not disabled when team enforce is lifted (#2466) (#2482)
Browse files Browse the repository at this point in the history
  • Loading branch information
MohamadJaara committed Dec 4, 2023
1 parent fad3498 commit 3634957
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 19 deletions.
35 changes: 22 additions & 13 deletions app/src/main/kotlin/com/wire/android/datastore/GlobalDataStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.wire.android.BuildConfig
import com.wire.android.feature.AppLockSource
import com.wire.android.migration.failure.UserMigrationStatus
import com.wire.android.ui.theme.ThemeOption
import com.wire.android.util.sha256
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
Expand All @@ -55,9 +57,12 @@ class GlobalDataStore @Inject constructor(@ApplicationContext private val contex
private val IS_LOGGING_ENABLED = booleanPreferencesKey("is_logging_enabled")
private val IS_ENCRYPTED_PROTEUS_STORAGE_ENABLED =
booleanPreferencesKey("is_encrypted_proteus_storage_enabled")
private val APP_LOCK_PASSCODE = stringPreferencesKey("app_lock_passcode")
private val APP_LOCK_PASSCODE = stringPreferencesKey("app_lock_passcode")
private val APP_LOCK_SOURCE = intPreferencesKey("app_lock_source")

val APP_THEME_OPTION = stringPreferencesKey("app_theme_option")
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = PREFERENCES_NAME)

private fun userMigrationStatusKey(userId: String): Preferences.Key<Int> =
intPreferencesKey("user_migration_status_$userId")

Expand Down Expand Up @@ -195,32 +200,36 @@ class GlobalDataStore @Inject constructor(@ApplicationContext private val contex
suspend fun clearAppLockPasscode() {
context.dataStore.edit {
it.remove(APP_LOCK_PASSCODE)
it.remove(APP_LOCK_SOURCE)
}
}

suspend fun getAppLockSource(): AppLockSource {
return context.dataStore.data.map {
it[APP_LOCK_SOURCE]?.let { source ->
AppLockSource.fromInt(source)
}
}.firstOrNull() ?: AppLockSource.Manual
}

@Suppress("TooGenericExceptionCaught")
private suspend fun setAppLockPasscode(
suspend fun setUserAppLock(
passcode: String,
key: Preferences.Key<String>
source: AppLockSource
) {
context.dataStore.edit {
try {
val hash = passcode.sha256()
val encrypted =
EncryptionManager.encrypt(key.name, passcode)
it[key] = encrypted
EncryptionManager.encrypt(APP_LOCK_PASSCODE.name, hash)
it[APP_LOCK_PASSCODE] = encrypted
it[APP_LOCK_SOURCE] = source.code
} catch (e: Exception) {
it.remove(key)
it.remove(APP_LOCK_PASSCODE)
}
}
}

suspend fun setUserAppLock(
passcode: String,
key: Preferences.Key<String> = APP_LOCK_PASSCODE
) {
setAppLockPasscode(passcode, key)
}

suspend fun setThemeOption(option: ThemeOption) {
context.dataStore.edit { it[APP_THEME_OPTION] = option.toString() }
}
Expand Down
38 changes: 38 additions & 0 deletions app/src/main/kotlin/com/wire/android/feature/AppLockSource.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.android.feature

sealed interface AppLockSource {
val code: Int
get() = when (this) {
is Manual -> 0
is TeamEnforced -> 1
}
data object Manual : AppLockSource
data object TeamEnforced : AppLockSource

companion object {
fun fromInt(value: Int): AppLockSource {
return when (value) {
0 -> Manual
1 -> TeamEnforced
else -> throw IllegalArgumentException("Unknown AppLockSource value: $value")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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.android.feature

import com.wire.android.datastore.GlobalDataStore
import com.wire.kalium.logic.feature.featureConfig.IsAppLockEditableUseCase
import dagger.hilt.android.scopes.ViewModelScoped
import javax.inject.Inject

@ViewModelScoped
class DisableAppLockUseCase @Inject constructor(
private val dataStore: GlobalDataStore,
private val isAppLockEditableUseCase: IsAppLockEditableUseCase
) {
suspend operator fun invoke(): Boolean = if (isAppLockEditableUseCase()) {
dataStore.clearAppLockPasscode()
true
} else {
false
}
}
1 change: 1 addition & 0 deletions app/src/main/kotlin/com/wire/android/ui/WireActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ class WireActivity : AppCompatActivity() {
} else {
with(featureFlagNotificationViewModel) {
markTeamAppLockStatusAsNot()
confirmAppLockNotEnforced()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wire.android.datastore.GlobalDataStore
import com.wire.android.feature.AppLockSource
import com.wire.android.feature.ObserveAppLockConfigUseCase
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.android.util.sha256
import com.wire.kalium.logic.feature.applock.MarkTeamAppLockStatusAsNotifiedUseCase
import com.wire.kalium.logic.feature.auth.ValidatePasswordUseCase
import com.wire.kalium.logic.feature.featureConfig.IsAppLockEditableUseCase
Expand All @@ -43,6 +43,7 @@ class SetLockScreenViewModel @Inject constructor(
private val dispatchers: DispatcherProvider,
private val observeAppLockConfig: ObserveAppLockConfigUseCase,
private val isAppLockEditable: IsAppLockEditableUseCase,
private val isAppLockEditableUseCase: IsAppLockEditableUseCase,
private val markTeamAppLockStatusAsNotified: MarkTeamAppLockStatusAsNotifiedUseCase
) : ViewModel() {

Expand Down Expand Up @@ -84,9 +85,15 @@ class SetLockScreenViewModel @Inject constructor(
viewModelScope.launch {
withContext(dispatchers.io()) {
with(globalDataStore) {
setUserAppLock(state.password.text.sha256())
val source = if (isAppLockEditableUseCase()) {
AppLockSource.Manual
} else {
AppLockSource.TeamEnforced
}

// TODO: call only when needed
setUserAppLock(state.password.text, source)

// TODO(bug): this does not take into account which account enforced the app lock
markTeamAppLockStatusAsNotified()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import androidx.lifecycle.viewModelScope
import com.wire.android.appLogger
import com.wire.android.datastore.GlobalDataStore
import com.wire.android.di.KaliumCoreLogic
import com.wire.android.feature.AppLockSource
import com.wire.android.feature.DisableAppLockUseCase
import com.wire.android.ui.home.FeatureFlagState
import com.wire.android.ui.home.conversations.selfdeletion.SelfDeletionMapper.toSelfDeletionDuration
import com.wire.android.ui.home.messagecomposer.SelfDeletionDuration
Expand All @@ -51,7 +53,8 @@ import javax.inject.Inject
class FeatureFlagNotificationViewModel @Inject constructor(
@KaliumCoreLogic private val coreLogic: CoreLogic,
private val currentSessionUseCase: CurrentSessionUseCase,
private val globalDataStore: GlobalDataStore
private val globalDataStore: GlobalDataStore,
private val disableAppLockUseCase: DisableAppLockUseCase
) : ViewModel() {

var featureFlagState by mutableStateOf(FeatureFlagState())
Expand Down Expand Up @@ -239,6 +242,16 @@ class FeatureFlagNotificationViewModel @Inject constructor(
}
}

fun confirmAppLockNotEnforced() {
viewModelScope.launch {
when (globalDataStore.getAppLockSource()) {
AppLockSource.Manual -> {}

AppLockSource.TeamEnforced -> disableAppLockUseCase()
}
}
}

fun isUserAppLockSet() = globalDataStore.isAppLockPasscodeSet()

fun getE2EICertificate() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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.android.feature

import com.wire.android.datastore.GlobalDataStore
import com.wire.kalium.logic.feature.featureConfig.IsAppLockEditableUseCase
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test

class DisableAppLockUseCaseTest {

@Test
fun `given app lock is editable when disable app lock is called then clear app lock passcode`() = runTest {
val (arrangement, useCase) = Arrangement()
.withAppLockEditable(true)
.withClearAppLockPasscode()
.arrange()

useCase()

coVerify(exactly = 1) { arrangement.dataStore.clearAppLockPasscode() }
}

@Test
fun `given app lock is not editable when disable app lock is called then do not clear app lock passcode`() = runTest {
val (arrangement, useCase) = Arrangement()
.withAppLockEditable(false)
.arrange()

useCase()

coVerify(exactly = 0) { arrangement.dataStore.clearAppLockPasscode() }
}
private class Arrangement {

init {
MockKAnnotations.init(this, relaxUnitFun = true)
}

@MockK
lateinit var dataStore: GlobalDataStore

@MockK
lateinit var isAppLockEditableUseCase: IsAppLockEditableUseCase

private val useCase = DisableAppLockUseCase(
dataStore,
isAppLockEditableUseCase
)

fun withAppLockEditable(result: Boolean) = apply {
coEvery { isAppLockEditableUseCase() } returns result
}

fun withClearAppLockPasscode() = apply {
coEvery { dataStore.clearAppLockPasscode() } returns Unit
}

fun arrange() = Pair(this, useCase)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,12 @@ class SetLockScreenViewModelTest {
@MockK
private lateinit var isAppLockEditable: IsAppLockEditableUseCase

@MockK
private lateinit var isAppLockEditableUseCase: IsAppLockEditableUseCase

init {
MockKAnnotations.init(this, relaxUnitFun = true)
coEvery { globalDataStore.setUserAppLock(any()) } returns Unit
coEvery { globalDataStore.setUserAppLock(any(), any()) } returns Unit
coEvery { observeAppLockConfig() } returns flowOf(
AppLockConfig.Disabled(ObserveAppLockConfigUseCase.DEFAULT_APP_LOCK_TIMEOUT)
)
Expand All @@ -102,12 +105,17 @@ class SetLockScreenViewModelTest {
every { validatePassword(any()) } returns ValidatePasswordResult.Invalid()
}

fun withIsAppLockEditable(result: Boolean) = apply {
coEvery { isAppLockEditableUseCase() } returns result
}

private val viewModel = SetLockScreenViewModel(
validatePassword,
globalDataStore,
TestDispatcherProvider(),
observeAppLockConfig,
isAppLockEditable,
isAppLockEditableUseCase,
markTeamAppLockStatusAsNotified
)

Expand Down

0 comments on commit 3634957

Please sign in to comment.