Skip to content

Commit

Permalink
[PM-18050] Remove pin policy (#4718)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrebispo5 authored Feb 12, 2025
1 parent 00f30c9 commit 2aa371a
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ enum class PolicyTypeJson {
@SerialName("11")
ACTIVATE_AUTOFILL,

/**
* Hides the setting to "Unlock with Pin".
*/
@SerialName("14")
REMOVE_UNLOCK_WITH_PIN,

/**
* Represents an unknown policy type.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,19 +258,21 @@ fun AccountSecurityScreen(
.fillMaxWidth()
.standardHorizontalMargin(),
)
Spacer(modifier = Modifier.height(height = 8.dp))
BitwardenUnlockWithPinSwitch(
isUnlockWithPasswordEnabled = state.isUnlockWithPasswordEnabled,
isUnlockWithPinEnabled = state.isUnlockWithPinEnabled,
onUnlockWithPinToggleAction = remember(viewModel) {
{ viewModel.trySendAction(AccountSecurityAction.UnlockWithPinToggle(it)) }
},
cardStyle = CardStyle.Full,
modifier = Modifier
.testTag("UnlockWithPinSwitch")
.fillMaxWidth()
.standardHorizontalMargin(),
)
if (!state.removeUnlockWithPinPolicyEnabled || state.isUnlockWithPinEnabled) {
Spacer(modifier = Modifier.height(height = 8.dp))
BitwardenUnlockWithPinSwitch(
isUnlockWithPasswordEnabled = state.isUnlockWithPasswordEnabled,
isUnlockWithPinEnabled = state.isUnlockWithPinEnabled,
onUnlockWithPinToggleAction = remember(viewModel) {
{ viewModel.trySendAction(AccountSecurityAction.UnlockWithPinToggle(it)) }
},
cardStyle = CardStyle.Full,
modifier = Modifier
.testTag("UnlockWithPinSwitch")
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
Spacer(Modifier.height(16.dp))
if (state.shouldShowEnableAuthenticatorSync) {
SyncWithAuthenticatorRow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class AccountSecurityViewModel @Inject constructor(
vaultTimeoutPolicyMinutes = null,
vaultTimeoutPolicyAction = null,
shouldShowUnlockActionCard = false,
removeUnlockWithPinPolicyEnabled = false,
)
},
) {
Expand Down Expand Up @@ -111,6 +112,16 @@ class AccountSecurityViewModel @Inject constructor(
.onEach(::sendAction)
.launchIn(viewModelScope)

policyManager
.getActivePoliciesFlow(type = PolicyTypeJson.REMOVE_UNLOCK_WITH_PIN)
.map { policies ->
AccountSecurityAction.Internal.RemovePinPolicyUpdateReceive(
removeUnlockWithPinPolicyEnabled = policies.isNotEmpty(),
)
}
.onEach(::sendAction)
.launchIn(viewModelScope)

featureFlagManager
.getFeatureFlagFlow(FlagKey.AuthenticatorSync)
.onEach {
Expand Down Expand Up @@ -390,6 +401,20 @@ class AccountSecurityViewModel @Inject constructor(
is AccountSecurityAction.Internal.PinProtectedLockUpdate -> {
handlePinProtectedLockUpdate(action)
}

is AccountSecurityAction.Internal.RemovePinPolicyUpdateReceive -> {
handleRemovePinPolicyUpdate(action)
}
}
}

private fun handleRemovePinPolicyUpdate(
action: AccountSecurityAction.Internal.RemovePinPolicyUpdateReceive,
) {
mutableStateFlow.update {
it.copy(
removeUnlockWithPinPolicyEnabled = action.removeUnlockWithPinPolicyEnabled,
)
}
}

Expand Down Expand Up @@ -524,6 +549,7 @@ data class AccountSecurityState(
val vaultTimeoutPolicyMinutes: Int?,
val vaultTimeoutPolicyAction: String?,
val shouldShowUnlockActionCard: Boolean,
val removeUnlockWithPinPolicyEnabled: Boolean,
) : Parcelable {
/**
* Indicates that there is a mechanism for unlocking your vault in place.
Expand Down Expand Up @@ -787,6 +813,13 @@ sealed class AccountSecurityAction {
val vaultTimeoutPolicies: List<PolicyInformation.VaultTimeout>?,
) : Internal()

/**
* A remove pin policy update has been received.
*/
data class RemovePinPolicyUpdateReceive(
val removeUnlockWithPinPolicyEnabled: Boolean,
) : Internal()

/**
* The show unlock badge update has been received.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,28 @@ class AccountSecurityScreenTest : BaseComposeTest() {
composeTestRule.onNodeWithText("Unlock with Biometrics").assertIsOn()
}

@Test
fun `unlock with pin toggle should be displayed according to state`() {
val toggleText = "Unlock with PIN code"
composeTestRule.onNodeWithText(toggleText).performScrollTo().assertIsDisplayed()

mutableStateFlow.update {
DEFAULT_STATE.copy(
removeUnlockWithPinPolicyEnabled = true,
isUnlockWithPinEnabled = true,
)
}
composeTestRule.onNodeWithText(toggleText).performScrollTo().assertIsDisplayed()

mutableStateFlow.update {
DEFAULT_STATE.copy(
removeUnlockWithPinPolicyEnabled = true,
isUnlockWithPinEnabled = false,
)
}
composeTestRule.onNodeWithText(toggleText).assertDoesNotExist()
}

@Test
fun `on unlock with pin toggle when enabled should send UnlockWithPinToggle Disabled`() {
mutableStateFlow.update {
Expand Down Expand Up @@ -1541,4 +1563,5 @@ private val DEFAULT_STATE = AccountSecurityState(
vaultTimeoutPolicyMinutes = null,
vaultTimeoutPolicyAction = null,
shouldShowUnlockActionCard = false,
removeUnlockWithPinPolicyEnabled = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentReposito
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson.Policy
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPolicy
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
Expand Down Expand Up @@ -81,7 +81,8 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
every { firstTimeStateFlow } returns mutableFirstTimeStateFlow
every { storeShowUnlockSettingBadge(any()) } just runs
}
private val mutableActivePolicyFlow = bufferedMutableSharedFlow<List<SyncResponseJson.Policy>>()
private val mutableActivePolicyFlow = bufferedMutableSharedFlow<List<Policy>>()
private val mutableRemovePinPolicyFlow = bufferedMutableSharedFlow<List<Policy>>()
private val biometricsEncryptionManager: BiometricsEncryptionManager = mockk {
every { createCipherOrNull(DEFAULT_USER_STATE.activeUserId) } returns CIPHER
every { getOrCreateCipher(DEFAULT_USER_STATE.activeUserId) } returns CIPHER
Expand All @@ -96,6 +97,9 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
every {
getActivePoliciesFlow(type = PolicyTypeJson.MAXIMUM_VAULT_TIMEOUT)
} returns mutableActivePolicyFlow
every {
getActivePoliciesFlow(type = PolicyTypeJson.REMOVE_UNLOCK_WITH_PIN)
} returns mutableRemovePinPolicyFlow
}
private val featureFlagManager: FeatureFlagManager = mockk(relaxed = true) {
every { getFeatureFlag(FlagKey.AuthenticatorSync) } returns false
Expand Down Expand Up @@ -164,6 +168,29 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
}
}

@Test
fun `state updates when remove pin policies change`() = runTest {
val viewModel = createViewModel()

mutableRemovePinPolicyFlow.emit(
listOf(
createMockPolicy(
isEnabled = true,
type = PolicyTypeJson.REMOVE_UNLOCK_WITH_PIN,
),
),
)

viewModel.stateFlow.test {
assertEquals(
DEFAULT_STATE.copy(
removeUnlockWithPinPolicyEnabled = true,
),
awaitItem(),
)
}
}

@Suppress("MaxLineLength")
@Test
fun `on AuthenticatorSyncToggle should update SettingsRepository and isAuthenticatorSyncChecked`() =
Expand Down Expand Up @@ -907,4 +934,5 @@ private val DEFAULT_STATE: AccountSecurityState = AccountSecurityState(
vaultTimeoutPolicyAction = null,
shouldShowEnableAuthenticatorSync = false,
shouldShowUnlockActionCard = false,
removeUnlockWithPinPolicyEnabled = false,
)

0 comments on commit 2aa371a

Please sign in to comment.