mirror of
https://github.com/bitwarden/android.git
synced 2025-12-11 13:57:03 -06:00
PM-23910: Disallow file sends for non-premium users (#5544)
This commit is contained in:
parent
25680f9255
commit
e75d7844de
@ -15,6 +15,7 @@ import com.bitwarden.ui.util.Text
|
|||||||
import com.bitwarden.ui.util.asText
|
import com.bitwarden.ui.util.asText
|
||||||
import com.bitwarden.ui.util.concat
|
import com.bitwarden.ui.util.concat
|
||||||
import com.x8bit.bitwarden.R
|
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.PolicyInformation
|
||||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||||
@ -65,6 +66,7 @@ private const val MAX_FILE_SIZE_BYTES: Long = 100 * 1024 * 1024
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AddEditSendViewModel @Inject constructor(
|
class AddEditSendViewModel @Inject constructor(
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
|
authRepo: AuthRepository,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
private val clipboardManager: BitwardenClipboardManager,
|
private val clipboardManager: BitwardenClipboardManager,
|
||||||
private val environmentRepo: EnvironmentRepository,
|
private val environmentRepo: EnvironmentRepository,
|
||||||
@ -131,6 +133,7 @@ class AddEditSendViewModel @Inject constructor(
|
|||||||
policyDisablesSend = policyManager
|
policyDisablesSend = policyManager
|
||||||
.getActivePolicies(type = PolicyTypeJson.DISABLE_SEND)
|
.getActivePolicies(type = PolicyTypeJson.DISABLE_SEND)
|
||||||
.any(),
|
.any(),
|
||||||
|
isPremium = authRepo.userStateFlow.value?.activeAccount?.isPremium == true,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@ -499,6 +502,19 @@ class AddEditSendViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
(content.selectedType as? AddEditSendState.ViewState.Content.SendType.File)
|
(content.selectedType as? AddEditSendState.ViewState.Content.SendType.File)
|
||||||
?.let { fileType ->
|
?.let { fileType ->
|
||||||
|
if (!state.isPremium) {
|
||||||
|
// We should never get here without a premium account, but we do one last
|
||||||
|
// check just in case.
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
dialogState = AddEditSendState.DialogState.Error(
|
||||||
|
title = R.string.send.asText(),
|
||||||
|
message = R.string.send_file_premium_required.asText(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return@onContent
|
||||||
|
}
|
||||||
if (fileType.name.isNullOrBlank()) {
|
if (fileType.name.isNullOrBlank()) {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@ -686,6 +702,7 @@ data class AddEditSendState(
|
|||||||
val isShared: Boolean,
|
val isShared: Boolean,
|
||||||
val baseWebSendUrl: String,
|
val baseWebSendUrl: String,
|
||||||
val policyDisablesSend: Boolean,
|
val policyDisablesSend: Boolean,
|
||||||
|
val isPremium: Boolean,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -838,15 +838,30 @@ class VaultItemListingViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is VaultItemListingState.ItemListingType.Send -> {
|
is VaultItemListingState.ItemListingType.Send -> {
|
||||||
sendEvent(
|
when (val sendType = itemListingType.toSendItemType()) {
|
||||||
VaultItemListingEvent.NavigateToAddSendItem(
|
SendItemType.FILE -> {
|
||||||
sendType = itemListingType.toSendItemType(),
|
if (state.isPremium) {
|
||||||
|
sendEvent(VaultItemListingEvent.NavigateToAddSendItem(sendType))
|
||||||
|
} else {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
dialogState = VaultItemListingState.DialogState.Error(
|
||||||
|
title = R.string.send.asText(),
|
||||||
|
message = R.string.send_file_premium_required.asText(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SendItemType.TEXT -> {
|
||||||
|
sendEvent(VaultItemListingEvent.NavigateToAddSendItem(sendType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleViewSendClick(action: ListingItemOverflowAction.SendAction.ViewClick) {
|
private fun handleViewSendClick(action: ListingItemOverflowAction.SendAction.ViewClick) {
|
||||||
sendEvent(
|
sendEvent(
|
||||||
VaultItemListingEvent.NavigateToViewSendItem(
|
VaultItemListingEvent.NavigateToViewSendItem(
|
||||||
|
|||||||
@ -881,4 +881,5 @@ private val DEFAULT_STATE = AddEditSendState(
|
|||||||
baseWebSendUrl = "https://vault.bitwarden.com/#/send/",
|
baseWebSendUrl = "https://vault.bitwarden.com/#/send/",
|
||||||
policyDisablesSend = false,
|
policyDisablesSend = false,
|
||||||
sendType = SendItemType.TEXT,
|
sendType = SendItemType.TEXT,
|
||||||
|
isPremium = true,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -12,10 +12,14 @@ import com.bitwarden.ui.platform.base.BaseViewModelTest
|
|||||||
import com.bitwarden.ui.util.Text
|
import com.bitwarden.ui.util.Text
|
||||||
import com.bitwarden.ui.util.asText
|
import com.bitwarden.ui.util.asText
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||||
|
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.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.PolicyManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
||||||
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager
|
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager
|
||||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
||||||
@ -66,6 +70,10 @@ class AddEditSendViewModelTest : BaseViewModelTest() {
|
|||||||
private val clipboardManager: BitwardenClipboardManager = mockk {
|
private val clipboardManager: BitwardenClipboardManager = mockk {
|
||||||
every { setText(any<String>(), toastDescriptorOverride = any<Text>()) } just runs
|
every { setText(any<String>(), toastDescriptorOverride = any<Text>()) } just runs
|
||||||
}
|
}
|
||||||
|
private val mutableUserStateFlow = MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
|
||||||
|
private val authRepository = mockk<AuthRepository> {
|
||||||
|
every { userStateFlow } returns mutableUserStateFlow
|
||||||
|
}
|
||||||
private val environmentRepository: EnvironmentRepository = mockk {
|
private val environmentRepository: EnvironmentRepository = mockk {
|
||||||
every { environment } returns Environment.Us
|
every { environment } returns Environment.Us
|
||||||
}
|
}
|
||||||
@ -386,6 +394,38 @@ class AddEditSendViewModelTest : BaseViewModelTest() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `SaveClick for file without premium show error dialog`() {
|
||||||
|
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
|
||||||
|
accounts = listOf(DEFAULT_ACCOUNT.copy(isPremium = false)),
|
||||||
|
)
|
||||||
|
val initialState = DEFAULT_STATE.copy(
|
||||||
|
isPremium = false,
|
||||||
|
viewState = DEFAULT_VIEW_STATE.copy(
|
||||||
|
common = DEFAULT_COMMON_STATE.copy(name = "test"),
|
||||||
|
selectedType = AddEditSendState.ViewState.Content.SendType.File(
|
||||||
|
uri = null,
|
||||||
|
name = null,
|
||||||
|
displaySize = null,
|
||||||
|
sizeBytes = null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val viewModel = createViewModel(initialState)
|
||||||
|
|
||||||
|
viewModel.trySendAction(AddEditSendAction.SaveClick)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
initialState.copy(
|
||||||
|
dialogState = AddEditSendState.DialogState.Error(
|
||||||
|
title = R.string.send.asText(),
|
||||||
|
message = R.string.send_file_premium_required.asText(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `SaveClick with file missing should show error dialog`() {
|
fun `SaveClick with file missing should show error dialog`() {
|
||||||
val initialState = DEFAULT_STATE.copy(
|
val initialState = DEFAULT_STATE.copy(
|
||||||
@ -966,6 +1006,7 @@ class AddEditSendViewModelTest : BaseViewModelTest() {
|
|||||||
toAddEditSendArgs()
|
toAddEditSendArgs()
|
||||||
} returns AddEditSendArgs(sendType = sendType, addEditSendType = addEditSendType)
|
} returns AddEditSendArgs(sendType = sendType, addEditSendType = addEditSendType)
|
||||||
},
|
},
|
||||||
|
authRepo = authRepository,
|
||||||
environmentRepo = environmentRepository,
|
environmentRepo = environmentRepository,
|
||||||
specialCircumstanceManager = specialCircumstanceManager,
|
specialCircumstanceManager = specialCircumstanceManager,
|
||||||
clock = clock,
|
clock = clock,
|
||||||
@ -1013,4 +1054,30 @@ private val DEFAULT_STATE = AddEditSendState(
|
|||||||
baseWebSendUrl = DEFAULT_ENVIRONMENT_URL,
|
baseWebSendUrl = DEFAULT_ENVIRONMENT_URL,
|
||||||
policyDisablesSend = false,
|
policyDisablesSend = false,
|
||||||
sendType = SendItemType.TEXT,
|
sendType = SendItemType.TEXT,
|
||||||
|
isPremium = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val DEFAULT_ACCOUNT = UserState.Account(
|
||||||
|
userId = "activeUserId",
|
||||||
|
name = "Active User",
|
||||||
|
email = "active@bitwarden.com",
|
||||||
|
environment = Environment.Us,
|
||||||
|
avatarColorHex = "#aa00aa",
|
||||||
|
isPremium = true,
|
||||||
|
isLoggedIn = true,
|
||||||
|
isVaultUnlocked = true,
|
||||||
|
needsPasswordReset = false,
|
||||||
|
isBiometricsEnabled = false,
|
||||||
|
organizations = emptyList(),
|
||||||
|
needsMasterPassword = false,
|
||||||
|
trustedDevice = null,
|
||||||
|
hasMasterPassword = true,
|
||||||
|
isUsingKeyConnector = false,
|
||||||
|
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||||
|
firstTimeState = FirstTimeState(showImportLoginsCard = true),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val DEFAULT_USER_STATE = UserState(
|
||||||
|
activeUserId = "activeUserId",
|
||||||
|
accounts = listOf(DEFAULT_ACCOUNT),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1422,7 +1422,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `AddVaultItemClick for send item should emit NavigateToAddVaultItem`() = runTest {
|
fun `AddVaultItemClick for text send item should emit NavigateToAddVaultItem`() = runTest {
|
||||||
val viewModel = createVaultItemListingViewModel(
|
val viewModel = createVaultItemListingViewModel(
|
||||||
createSavedStateHandleWithVaultItemListingType(VaultItemListingType.SendText),
|
createSavedStateHandleWithVaultItemListingType(VaultItemListingType.SendText),
|
||||||
)
|
)
|
||||||
@ -1435,6 +1435,47 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `AddVaultItemClick for file send item with premium should emit NavigateToAddVaultItem`() =
|
||||||
|
runTest {
|
||||||
|
val viewModel = createVaultItemListingViewModel(
|
||||||
|
createSavedStateHandleWithVaultItemListingType(VaultItemListingType.SendFile),
|
||||||
|
)
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick)
|
||||||
|
assertEquals(
|
||||||
|
VaultItemListingEvent.NavigateToAddSendItem(sendType = SendItemType.FILE),
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `AddVaultItemClick for file send item without premium should display error dialog`() =
|
||||||
|
runTest {
|
||||||
|
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
|
||||||
|
accounts = listOf(DEFAULT_ACCOUNT.copy(isPremium = false)),
|
||||||
|
)
|
||||||
|
val viewModel = createVaultItemListingViewModel(
|
||||||
|
createSavedStateHandleWithVaultItemListingType(VaultItemListingType.SendFile),
|
||||||
|
)
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick)
|
||||||
|
expectNoEvents()
|
||||||
|
}
|
||||||
|
assertEquals(
|
||||||
|
createVaultItemListingState(
|
||||||
|
itemListingType = VaultItemListingState.ItemListingType.Send.SendFile,
|
||||||
|
dialogState = VaultItemListingState.DialogState.Error(
|
||||||
|
title = R.string.send.asText(),
|
||||||
|
message = R.string.send_file_premium_required.asText(),
|
||||||
|
),
|
||||||
|
isPremium = false,
|
||||||
|
),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ItemTypeToAddSelected sends NavigateToAddFolder for folder selection`() = runTest {
|
fun `ItemTypeToAddSelected sends NavigateToAddFolder for folder selection`() = runTest {
|
||||||
val viewModel = createVaultItemListingViewModel(
|
val viewModel = createVaultItemListingViewModel(
|
||||||
@ -5331,6 +5372,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
itemListingType: VaultItemListingState.ItemListingType = VaultItemListingState.ItemListingType.Vault.Login,
|
itemListingType: VaultItemListingState.ItemListingType = VaultItemListingState.ItemListingType.Vault.Login,
|
||||||
viewState: VaultItemListingState.ViewState = VaultItemListingState.ViewState.Loading,
|
viewState: VaultItemListingState.ViewState = VaultItemListingState.ViewState.Loading,
|
||||||
dialogState: VaultItemListingState.DialogState? = null,
|
dialogState: VaultItemListingState.DialogState? = null,
|
||||||
|
isPremium: Boolean = true,
|
||||||
): VaultItemListingState =
|
): VaultItemListingState =
|
||||||
VaultItemListingState(
|
VaultItemListingState(
|
||||||
itemListingType = itemListingType,
|
itemListingType = itemListingType,
|
||||||
@ -5348,7 +5390,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||||||
policyDisablesSend = false,
|
policyDisablesSend = false,
|
||||||
hasMasterPassword = true,
|
hasMasterPassword = true,
|
||||||
createCredentialRequest = null,
|
createCredentialRequest = null,
|
||||||
isPremium = true,
|
isPremium = isPremium,
|
||||||
isRefreshing = false,
|
isRefreshing = false,
|
||||||
restrictItemTypesPolicyOrgIds = persistentListOf(),
|
restrictItemTypesPolicyOrgIds = persistentListOf(),
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user