diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/model/PolicyInformation.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/model/PolicyInformation.kt index 2a34e6dd14..9efa76da21 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/model/PolicyInformation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/model/PolicyInformation.kt @@ -99,6 +99,18 @@ sealed class PolicyInformation { } } + /** + * Represents a policy enforcing rules on the user's add & edit send options. + * + * @property shouldDisableHideEmail Indicates whether the user should have the ability to hide + * their email address from send recipients. + */ + @Serializable + data class SendOptions( + @SerialName("disableHideEmail") + val shouldDisableHideEmail: Boolean?, + ) : PolicyInformation() + /** * Represents a policy enforcing rules on the user's vault timeout settings. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/SyncResponseJsonExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/SyncResponseJsonExtensions.kt index 121f547f2a..22119548c4 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/SyncResponseJsonExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/SyncResponseJsonExtensions.kt @@ -40,6 +40,9 @@ val SyncResponseJson.Policy.policyInformation: PolicyInformation? PolicyTypeJson.MAXIMUM_VAULT_TIMEOUT -> { Json.decodeFromStringOrNull(it) } + PolicyTypeJson.SEND_OPTIONS -> { + Json.decodeFromStringOrNull(it) + } else -> null } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/util/PolicyManagerExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/util/PolicyManagerExtensions.kt index d32c504a03..f36b83a211 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/util/PolicyManagerExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/util/PolicyManagerExtensions.kt @@ -12,6 +12,8 @@ inline fun PolicyManager.getActivePolicies(): Li val type = when (T::class.java) { PolicyInformation.MasterPassword::class.java -> PolicyTypeJson.MASTER_PASSWORD PolicyInformation.PasswordGenerator::class.java -> PolicyTypeJson.PASSWORD_GENERATOR + PolicyInformation.SendOptions::class.java -> PolicyTypeJson.SEND_OPTIONS + PolicyInformation.VaultTimeout::class.java -> PolicyTypeJson.MAXIMUM_VAULT_TIMEOUT else -> { throw IllegalStateException( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendContent.kt index 67c5577bfc..1bdd1cb516 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendContent.kt @@ -475,6 +475,7 @@ private fun AddSendOptions( isChecked = state.common.isHideEmailChecked, onCheckedChange = addSendHandlers.onHideEmailToggle, readOnly = sendRestrictionPolicy, + enabled = state.common.isHideEmailChecked || state.common.isHideEmailAddressEnabled, ) Spacer(modifier = Modifier.height(16.dp)) BitwardenWideSwitch( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt index 94f02976a6..430cd43c0b 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt @@ -7,10 +7,12 @@ import androidx.lifecycle.viewModelScope import com.bitwarden.core.SendView import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.auth.repository.AuthRepository +import com.x8bit.bitwarden.data.auth.repository.model.PolicyInformation import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager +import com.x8bit.bitwarden.data.platform.manager.util.getActivePolicies import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.model.DataState import com.x8bit.bitwarden.data.platform.repository.util.baseWebSendUrl @@ -88,6 +90,9 @@ class AddSendViewModel @Inject constructor( noteInput = "", isHideEmailChecked = false, isDeactivateChecked = false, + isHideEmailAddressEnabled = !policyManager + .getActivePolicies() + .any { it.shouldDisableHideEmail ?: false }, deletionDate = ZonedDateTime .now(clock) // We want the default time to be midnight, so we remove all values @@ -319,6 +324,7 @@ class AddSendViewModel @Inject constructor( .environment .environmentUrlData .baseWebSendUrl, + isHideEmailAddressEnabled = isHideEmailAddressEnabled, ) ?: AddSendState.ViewState.Error( message = R.string.generic_error_message.asText(), @@ -356,6 +362,7 @@ class AddSendViewModel @Inject constructor( .environment .environmentUrlData .baseWebSendUrl, + isHideEmailAddressEnabled = isHideEmailAddressEnabled, ) ?: AddSendState.ViewState.Error( message = R.string.generic_error_message.asText(), @@ -625,6 +632,11 @@ class AddSendViewModel @Inject constructor( ) } + private val isHideEmailAddressEnabled: Boolean + get() = !policyManager + .getActivePolicies() + .any { it.shouldDisableHideEmail ?: false } + private inline fun onContent( crossinline block: (AddSendState.ViewState.Content) -> Unit, ) { @@ -764,6 +776,7 @@ data class AddSendState( val noteInput: String, val isHideEmailChecked: Boolean, val isDeactivateChecked: Boolean, + val isHideEmailAddressEnabled: Boolean, val deletionDate: ZonedDateTime, val expirationDate: ZonedDateTime?, val sendUrl: String?, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensions.kt index 27894554d7..b9e16eb422 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensions.kt @@ -13,6 +13,7 @@ import java.time.ZonedDateTime fun SendView.toViewState( clock: Clock, baseWebSendUrl: String, + isHideEmailAddressEnabled: Boolean, ): AddSendState.ViewState.Content = AddSendState.ViewState.Content( common = AddSendState.ViewState.Content.Common( @@ -30,6 +31,7 @@ fun SendView.toViewState( expirationDate = this.expirationDate?.let { ZonedDateTime.ofInstant(it, clock.zone) }, sendUrl = this.toSendUrl(baseWebSendUrl), hasPassword = this.hasPassword, + isHideEmailAddressEnabled = isHideEmailAddressEnabled, ), selectedType = when (type) { SendType.TEXT -> { diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt index 94b2b76f12..449d50cafc 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt @@ -762,6 +762,49 @@ class AddSendScreenTest : BaseComposeTest() { .assertIsOn() } + @Test + fun `hide email toggle should be disabled according to state`() = runTest { + // Expand options section: + composeTestRule + .onNodeWithText("Options") + .performScrollTo() + .performClick() + + mutableStateFlow.update { + it.copy( + viewState = DEFAULT_VIEW_STATE.copy( + common = DEFAULT_COMMON_STATE.copy( + isHideEmailAddressEnabled = false, + ), + ), + ) + } + + // Toggle should be disabled + composeTestRule + .onNodeWithText("Hide my email address", substring = true) + .performScrollTo() + .assertIsDisplayed() + .assertIsNotEnabled() + + mutableStateFlow.update { + it.copy( + viewState = DEFAULT_VIEW_STATE.copy( + common = DEFAULT_COMMON_STATE.copy( + isHideEmailChecked = true, + ), + ), + ) + } + + // Toggle should be enabled + composeTestRule + .onNodeWithText("Hide my email address", substring = true) + .performScrollTo() + .assertIsDisplayed() + .assertIsEnabled() + } + @Test fun `on deactivate this send toggle should send DeactivateThisSendToggle`() = runTest { // Expand options section: @@ -957,6 +1000,7 @@ class AddSendScreenTest : BaseComposeTest() { expirationDate = null, sendUrl = null, hasPassword = true, + isHideEmailAddressEnabled = true, ) private val DEFAULT_SELECTED_TYPE_STATE = AddSendState.ViewState.Content.SendType.Text( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt index 027489e995..9a5cd54e79 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt @@ -6,6 +6,7 @@ import app.cash.turbine.test import com.bitwarden.core.SendView import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.auth.repository.AuthRepository +import com.x8bit.bitwarden.data.auth.repository.model.PolicyInformation import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.platform.manager.PolicyManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager @@ -14,6 +15,7 @@ import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.model.DataState import com.x8bit.bitwarden.data.platform.repository.model.Environment 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.createMockPolicy import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView import com.x8bit.bitwarden.data.vault.repository.VaultRepository @@ -39,6 +41,9 @@ import io.mockk.unmockkStatic import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonObject import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach @@ -75,6 +80,7 @@ class AddSendViewModelTest : BaseViewModelTest() { } private val policyManager: PolicyManager = mockk { every { getActivePolicies(type = PolicyTypeJson.DISABLE_SEND) } returns emptyList() + every { getActivePolicies(type = PolicyTypeJson.SEND_OPTIONS) } returns emptyList() } @BeforeEach @@ -101,6 +107,30 @@ class AddSendViewModelTest : BaseViewModelTest() { assertEquals(DEFAULT_STATE, viewModel.stateFlow.value) } + @Test + fun `initial state should be correct when a sendOption includes shouldDisableHideEmail`() { + every { + policyManager.getActivePolicies(type = PolicyTypeJson.SEND_OPTIONS) + } returns listOf( + SyncResponseJson.Policy( + id = "123", + type = PolicyTypeJson.SEND_OPTIONS, + isEnabled = true, + data = Json.encodeToJsonElement( + PolicyInformation.SendOptions(shouldDisableHideEmail = true), + ).jsonObject, + organizationId = "id2", + ), + ) + val viewModel = createViewModel() + val viewState = DEFAULT_VIEW_STATE.copy( + common = DEFAULT_COMMON_STATE.copy( + isHideEmailAddressEnabled = false, + ), + ) + assertEquals(DEFAULT_STATE.copy(viewState = viewState), viewModel.stateFlow.value) + } + @Test fun `initial state should read from saved state when present`() { val savedState = DEFAULT_STATE.copy( @@ -270,7 +300,13 @@ class AddSendViewModelTest : BaseViewModelTest() { viewState = viewState, ) val mockSendView = createMockSendView(number = 1) - every { mockSendView.toViewState(clock, DEFAULT_ENVIRONMENT_URL) } returns viewState + every { + mockSendView.toViewState( + clock = clock, + baseWebSendUrl = DEFAULT_ENVIRONMENT_URL, + isHideEmailAddressEnabled = true, + ) + } returns viewState every { viewState.toSendView(clock) } returns mockSendView val sendUrl = "www.test.com/send/test" val resultSendView = mockk { @@ -308,7 +344,13 @@ class AddSendViewModelTest : BaseViewModelTest() { every { id } returns sendId } val errorMessage = "Failure" - every { mockSendView.toViewState(clock, DEFAULT_ENVIRONMENT_URL) } returns viewState + every { + mockSendView.toViewState( + clock = clock, + baseWebSendUrl = DEFAULT_ENVIRONMENT_URL, + isHideEmailAddressEnabled = true, + ) + } returns viewState every { viewState.toSendView(clock) } returns mockSendView coEvery { vaultRepository.updateSend(sendId = sendId, sendView = mockSendView) @@ -426,7 +468,11 @@ class AddSendViewModelTest : BaseViewModelTest() { ) val mockSendView = createMockSendView(number = 1) every { - mockSendView.toViewState(clock = clock, baseWebSendUrl = DEFAULT_ENVIRONMENT_URL) + mockSendView.toViewState( + clock = clock, + baseWebSendUrl = DEFAULT_ENVIRONMENT_URL, + isHideEmailAddressEnabled = true, + ) } returns viewState mutableSendDataStateFlow.value = DataState.Loaded(mockSendView) val viewModel = createViewModel( @@ -467,7 +513,11 @@ class AddSendViewModelTest : BaseViewModelTest() { ) val mockSendView = createMockSendView(number = 1) every { - mockSendView.toViewState(clock, DEFAULT_ENVIRONMENT_URL) + mockSendView.toViewState( + clock = clock, + baseWebSendUrl = DEFAULT_ENVIRONMENT_URL, + isHideEmailAddressEnabled = true, + ) } returns DEFAULT_VIEW_STATE mutableSendDataStateFlow.value = DataState.Loaded(mockSendView) val viewModel = createViewModel( @@ -509,7 +559,11 @@ class AddSendViewModelTest : BaseViewModelTest() { } returns RemovePasswordSendResult.Error(errorMessage = errorMessage) val mockSendView = createMockSendView(number = 1) every { - mockSendView.toViewState(clock, DEFAULT_ENVIRONMENT_URL) + mockSendView.toViewState( + clock = clock, + baseWebSendUrl = DEFAULT_ENVIRONMENT_URL, + isHideEmailAddressEnabled = true, + ) } returns DEFAULT_VIEW_STATE mutableSendDataStateFlow.value = DataState.Loaded(mockSendView) val initialState = DEFAULT_STATE.copy( @@ -575,7 +629,11 @@ class AddSendViewModelTest : BaseViewModelTest() { ) val mockSendView = createMockSendView(number = 1) every { - mockSendView.toViewState(clock, DEFAULT_ENVIRONMENT_URL) + mockSendView.toViewState( + clock = clock, + baseWebSendUrl = DEFAULT_ENVIRONMENT_URL, + isHideEmailAddressEnabled = true, + ) } returns DEFAULT_VIEW_STATE mutableSendDataStateFlow.value = DataState.Loaded(mockSendView) val viewModel = createViewModel( @@ -630,7 +688,11 @@ class AddSendViewModelTest : BaseViewModelTest() { ) val mockSendView = createMockSendView(number = 1) every { - mockSendView.toViewState(clock = clock, baseWebSendUrl = DEFAULT_ENVIRONMENT_URL) + mockSendView.toViewState( + clock = clock, + baseWebSendUrl = DEFAULT_ENVIRONMENT_URL, + isHideEmailAddressEnabled = true, + ) } returns viewState mutableSendDataStateFlow.value = DataState.Loaded(mockSendView) val viewModel = createViewModel( @@ -997,6 +1059,7 @@ class AddSendViewModelTest : BaseViewModelTest() { expirationDate = null, sendUrl = null, hasPassword = false, + isHideEmailAddressEnabled = true, ) private val DEFAULT_SELECTED_TYPE_STATE = AddSendState.ViewState.Content.SendType.Text( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensionsTest.kt index 4c220061ff..f8959536e3 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensionsTest.kt @@ -77,6 +77,7 @@ private val DEFAULT_COMMON_STATE = AddSendState.ViewState.Content.Common( expirationDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"), sendUrl = null, hasPassword = false, + isHideEmailAddressEnabled = true, ) private val DEFAULT_SELECTED_TYPE_STATE = AddSendState.ViewState.Content.SendType.Text( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensionsTest.kt index b7cf8a0db4..200fa25e39 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/SendViewExtensionsTest.kt @@ -19,6 +19,7 @@ class SendViewExtensionsTest { val result = sendView.toViewState( clock = FIXED_CLOCK, baseWebSendUrl = "www.test.com/", + isHideEmailAddressEnabled = true, ) assertEquals( @@ -36,6 +37,7 @@ class SendViewExtensionsTest { val result = sendView.toViewState( clock = FIXED_CLOCK, baseWebSendUrl = "www.test.com/", + isHideEmailAddressEnabled = true, ) assertEquals( @@ -72,6 +74,7 @@ private val DEFAULT_COMMON: AddSendState.ViewState.Content.Common = ), sendUrl = "www.test.com/mockAccessId-1/mockKey-1", hasPassword = true, + isHideEmailAddressEnabled = true, ) private val DEFAULT_TEXT_TYPE: AddSendState.ViewState.Content.SendType.Text =