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: asset restriction [WPB-9947] (#3114) (#3147) #3150

Merged
merged 5 commits into from
Jul 3, 2024
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
3 changes: 2 additions & 1 deletion app/src/main/kotlin/com/wire/android/ui/WireActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import com.wire.android.ui.home.E2EICertificateRevokedDialog
import com.wire.android.ui.home.E2EIRequiredDialog
import com.wire.android.ui.home.E2EIResultDialog
import com.wire.android.ui.home.E2EISnoozeDialog
import com.wire.android.ui.home.FeatureFlagState
import com.wire.android.ui.home.appLock.LockCodeTimeManager
import com.wire.android.ui.home.sync.FeatureFlagNotificationViewModel
import com.wire.android.ui.legalhold.dialog.deactivated.LegalHoldDeactivatedDialog
Expand Down Expand Up @@ -328,7 +329,7 @@ class WireActivity : AppCompatActivity() {
}
if (showFileSharingDialog) {
FileRestrictionDialog(
isFileSharingEnabled = isFileSharingEnabledState,
isFileSharingEnabled = (isFileSharingState !is FeatureFlagState.FileSharingState.DisabledByTeam),
hideDialogStatus = featureFlagNotificationViewModel::dismissFileSharingDialog
)
}
Expand Down
11 changes: 7 additions & 4 deletions app/src/main/kotlin/com/wire/android/ui/home/FeatureFlagState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ import kotlin.time.Duration

data class FeatureFlagState(
val showFileSharingDialog: Boolean = false,
val isFileSharingEnabledState: Boolean = true,
val fileSharingRestrictedState: SharingRestrictedState? = null,
val isFileSharingState: FileSharingState = FileSharingState.NoUser,
val shouldShowGuestRoomLinkDialog: Boolean = false,
val shouldShowE2eiCertificateRevokedDialog: Boolean = false,
val shouldShowTeamAppLockDialog: Boolean = false,
Expand All @@ -40,8 +39,12 @@ data class FeatureFlagState(
val showCallEndedBecauseOfConversationDegraded: Boolean = false,
val startGettingE2EICertificate: Boolean = false
) {
enum class SharingRestrictedState {
NONE, NO_USER, RESTRICTED_IN_TEAM

sealed interface FileSharingState {
data object NoUser : FileSharingState
data object AllowAll : FileSharingState
data class AllowSome(val allowedList: List<String>) : FileSharingState
data object DisabledByTeam : FileSharingState
}

data class E2EISnooze(val timeLeft: Duration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ sealed class ConversationSnackbarMessages(override val uiText: UIText) : SnackBa
object ErrorDownloadingAsset : ConversationSnackbarMessages(UIText.StringResource(R.string.error_conversation_downloading_asset))
object ErrorOpeningAssetFile : ConversationSnackbarMessages(UIText.StringResource(R.string.error_conversation_opening_asset_file))
object ErrorDeletingMessage : ConversationSnackbarMessages(UIText.StringResource(R.string.error_conversation_deleting_message))
data object ErrorAssetRestriction : ConversationSnackbarMessages(UIText.StringResource(R.string.restricted_asset_error_toast_message))
data class ErrorMaxAssetSize(val maxLimitInMB: Int) :
ConversationSnackbarMessages(UIText.StringResource(R.string.error_conversation_max_asset_size_limit, maxLimitInMB))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ class MessageComposerViewModel @Inject constructor(
}
}

@Suppress("LongMethod")
internal fun sendAttachment(attachmentBundle: AssetBundle?) {
viewModelScope.launch {
withContext(dispatchers.io()) {
Expand All @@ -366,7 +367,9 @@ class MessageComposerViewModel @Inject constructor(
assetDataSize = dataSize,
assetMimeType = mimeType,
audioLengthInMs = 0L
).handleLegalHoldFailureAfterSendingMessage()
).also {
handleAssetSendingResult(it)
}
}

AttachmentType.VIDEO,
Expand All @@ -385,7 +388,9 @@ class MessageComposerViewModel @Inject constructor(
dataPath = dataPath,
mimeType = mimeType
)
).handleLegalHoldFailureAfterSendingMessage()
).also {
handleAssetSendingResult(it)
}
} catch (e: OutOfMemoryError) {
appLogger.e("There was an OutOfMemory error while uploading the asset")
onSnackbarMessage(ConversationSnackbarMessages.ErrorSendingAsset)
Expand All @@ -397,6 +402,23 @@ class MessageComposerViewModel @Inject constructor(
}
}

private suspend fun handleAssetSendingResult(result: ScheduleNewAssetMessageResult) {
when (result) {
is ScheduleNewAssetMessageResult.Failure.Generic -> {
result.coreFailure.handleLegalHoldFailureAfterSendingMessage()
}
is ScheduleNewAssetMessageResult.Success -> {
/* no-op */
}

ScheduleNewAssetMessageResult.Failure.DisabledByTeam,
ScheduleNewAssetMessageResult.Failure.RestrictedFileType -> {
withContext(dispatchers.main()) {
onSnackbarMessage(ConversationSnackbarMessages.ErrorAssetRestriction)
}
}
}
}
private fun CoreFailure.handleLegalHoldFailureAfterSendingMessage() = also {
if (this is LegalHoldEnabledForConversationFailure) {
sureAboutMessagingDialogState = SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.AfterSending(this.messageId)
Expand All @@ -406,12 +428,6 @@ class MessageComposerViewModel @Inject constructor(
private fun Either<CoreFailure, Unit>.handleLegalHoldFailureAfterSendingMessage() =
onFailure { it.handleLegalHoldFailureAfterSendingMessage() }

private fun ScheduleNewAssetMessageResult.handleLegalHoldFailureAfterSendingMessage() = also {
if (it is ScheduleNewAssetMessageResult.Failure) {
it.coreFailure.handleLegalHoldFailureAfterSendingMessage()
}
}

fun retrySendingMessage(messageId: String) {
viewModelScope.launch {
retryFailedMessage(messageId = messageId, conversationId = conversationId)
Expand Down Expand Up @@ -449,13 +465,12 @@ class MessageComposerViewModel @Inject constructor(
}

private fun setFileSharingStatus() {
// TODO: handle restriction when sending assets
viewModelScope.launch {
messageComposerViewState.value = when (isFileSharingEnabled().state) {
FileSharingStatus.Value.Disabled,
is FileSharingStatus.Value.EnabledSome ->
FileSharingStatus.Value.Disabled ->
messageComposerViewState.value.copy(isFileSharingEnabled = false)

is FileSharingStatus.Value.EnabledSome,
FileSharingStatus.Value.EnabledAll ->
messageComposerViewState.value.copy(isFileSharingEnabled = true)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ class FeatureFlagNotificationViewModel @Inject constructor(
currentUserId = null
appLogger.i("$TAG: Failure while getting current session")
featureFlagState = FeatureFlagState( // no session, clear feature flag state to default and set NO_USER
fileSharingRestrictedState = FeatureFlagState.SharingRestrictedState.NO_USER
isFileSharingState = FeatureFlagState.FileSharingState.NoUser
)
}

currentSessionResult is CurrentSessionResult.Success && !currentSessionResult.accountInfo.isValid() -> {
appLogger.i("$TAG: Invalid current session")
featureFlagState = FeatureFlagState( // invalid session, clear feature flag state to default and set NO_USER
fileSharingRestrictedState = FeatureFlagState.SharingRestrictedState.NO_USER
isFileSharingState = FeatureFlagState.FileSharingState.NoUser
)
}

Expand Down Expand Up @@ -132,22 +132,17 @@ class FeatureFlagNotificationViewModel @Inject constructor(

private suspend fun setFileSharingState(userId: UserId) {
coreLogic.getSessionScope(userId).observeFileSharingStatus().collect { fileSharingStatus ->
fileSharingStatus.state?.let {
// TODO: handle restriction when sending assets
val (fileSharingRestrictedState, state) = if (it is FileSharingStatus.Value.EnabledAll) {
FeatureFlagState.SharingRestrictedState.NONE to true
} else {
FeatureFlagState.SharingRestrictedState.RESTRICTED_IN_TEAM to false
}

featureFlagState = featureFlagState.copy(
fileSharingRestrictedState = fileSharingRestrictedState,
isFileSharingEnabledState = state
val state: FeatureFlagState.FileSharingState = when (fileSharingStatus.state) {
FileSharingStatus.Value.Disabled -> FeatureFlagState.FileSharingState.DisabledByTeam
FileSharingStatus.Value.EnabledAll -> FeatureFlagState.FileSharingState.AllowAll
is FileSharingStatus.Value.EnabledSome -> FeatureFlagState.FileSharingState.AllowSome(
(fileSharingStatus.state as FileSharingStatus.Value.EnabledSome).allowedType
)
}
fileSharingStatus.isStatusChanged?.let {
featureFlagState = featureFlagState.copy(showFileSharingDialog = it)
}
featureFlagState = featureFlagState.copy(
isFileSharingState = state,
showFileSharingDialog = fileSharingStatus.isStatusChanged ?: false
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Parcelable
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
Expand All @@ -31,6 +32,7 @@ import androidx.core.app.ShareCompat
import androidx.core.net.toUri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wire.android.R
import com.wire.android.appLogger
import com.wire.android.mapper.UserTypeMapper
import com.wire.android.mapper.toUIPreview
Expand Down Expand Up @@ -69,6 +71,7 @@ import com.wire.kalium.logic.feature.selfDeletingMessages.ObserveSelfDeletionTim
import com.wire.kalium.logic.feature.selfDeletingMessages.PersistNewSelfDeletionTimerUseCase
import com.wire.kalium.logic.feature.user.GetSelfUserUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
Expand All @@ -90,6 +93,7 @@ import javax.inject.Inject
@OptIn(FlowPreview::class)
@Suppress("LongParameterList", "TooManyFunctions")
class ImportMediaAuthenticatedViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val getSelf: GetSelfUserUseCase,
private val userTypeMapper: UserTypeMapper,
private val observeConversationListDetails: ObserveConversationListDetailsUseCase,
Expand Down Expand Up @@ -363,13 +367,26 @@ class ImportMediaAuthenticatedViewModel @Inject constructor(
mimeType = importedAsset.mimeType
)
).also {
val logConversationId = conversation.conversationId.toLogString()
if (it is ScheduleNewAssetMessageResult.Failure) {
appLogger.e("Failed to import asset message to " +
"conversationId=$logConversationId")
} else {
appLogger.d("Success importing asset message to " +
"conversationId=$logConversationId")
when (it) {
is ScheduleNewAssetMessageResult.Success -> appLogger.d(
"Successfully imported asset message to conversationId=${conversation.conversationId.toLogString()}"
)

is ScheduleNewAssetMessageResult.Failure.Generic -> appLogger.e(
"Failed to import asset message to conversationId=${conversation.conversationId.toLogString()}"
)

ScheduleNewAssetMessageResult.Failure.RestrictedFileType,
ScheduleNewAssetMessageResult.Failure.DisabledByTeam -> {
Toast.makeText(
context,
R.string.restricted_asset_error_toast_message,
Toast.LENGTH_SHORT
).show()
appLogger.e(
"Failed to import asset message to conversationId=${conversation.conversationId.toLogString()}"
)
}
}
}
}
Expand Down Expand Up @@ -497,5 +514,10 @@ data class ImportMediaAuthenticatedState(
val isImporting: Boolean = false,
val shareableConversationListState: ShareableConversationListState = ShareableConversationListState(),
val selectedConversationItem: List<ConversationItem> = emptyList(),
val selfDeletingTimer: SelfDeletionTimer = SelfDeletionTimer.Enabled(null)
val selfDeletingTimer: SelfDeletionTimer = SelfDeletionTimer.Enabled(null),
val assetSendError: AssetSendError? = null
)

enum class AssetSendError {
DISABLED_BY_TEAM, RESTRICTED_ASSET
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,15 @@ fun ImportMediaScreen(
navigator: Navigator,
featureFlagNotificationViewModel: FeatureFlagNotificationViewModel = hiltViewModel()
) {
when (val fileSharingRestrictedState =
featureFlagNotificationViewModel.featureFlagState.fileSharingRestrictedState) {
FeatureFlagState.SharingRestrictedState.NO_USER -> {
when (val fileSharingRestrictedState = featureFlagNotificationViewModel.featureFlagState.isFileSharingState) {
FeatureFlagState.FileSharingState.NoUser -> {
ImportMediaLoggedOutContent(
fileSharingRestrictedState = fileSharingRestrictedState,
navigateBack = navigator::navigateBack
)
}

FeatureFlagState.SharingRestrictedState.RESTRICTED_IN_TEAM -> {
FeatureFlagState.FileSharingState.DisabledByTeam -> {
val importMediaViewModel: ImportMediaAuthenticatedViewModel = hiltViewModel()
ImportMediaRestrictedContent(
fileSharingRestrictedState = fileSharingRestrictedState,
Expand All @@ -116,7 +115,8 @@ fun ImportMediaScreen(
)
}

FeatureFlagState.SharingRestrictedState.NONE -> {
FeatureFlagState.FileSharingState.AllowAll,
is FeatureFlagState.FileSharingState.AllowSome -> {
val importMediaViewModel: ImportMediaAuthenticatedViewModel = hiltViewModel()
ImportMediaRegularContent(
importMediaAuthenticatedState = importMediaViewModel.importMediaState,
Expand Down Expand Up @@ -144,18 +144,14 @@ fun ImportMediaScreen(
}
}
}

null -> {
// state is not calculated yet, need to wait to avoid crash while requesting currentUser where it's absent
}
}

BackHandler { navigator.navigateBack() }
}

@Composable
fun ImportMediaRestrictedContent(
fileSharingRestrictedState: FeatureFlagState.SharingRestrictedState,
fileSharingRestrictedState: FeatureFlagState.FileSharingState,
importMediaAuthenticatedState: ImportMediaAuthenticatedState,
navigateBack: () -> Unit,
) {
Expand Down Expand Up @@ -247,7 +243,7 @@ fun ImportMediaRegularContent(

@Composable
fun ImportMediaLoggedOutContent(
fileSharingRestrictedState: FeatureFlagState.SharingRestrictedState,
fileSharingRestrictedState: FeatureFlagState.FileSharingState,
navigateBack: () -> Unit,
) {
WireScaffold(
Expand All @@ -272,7 +268,7 @@ fun ImportMediaLoggedOutContent(
@Composable
fun FileSharingRestrictedContent(
internalPadding: PaddingValues,
sharingRestrictedState: FeatureFlagState.SharingRestrictedState,
sharingRestrictedState: FeatureFlagState.FileSharingState,
openWireAction: () -> Unit
) {
val context = LocalContext.current
Expand All @@ -287,7 +283,7 @@ fun FileSharingRestrictedContent(
.padding(horizontal = dimensions().spacing48x)
) {
val textRes =
if (sharingRestrictedState == FeatureFlagState.SharingRestrictedState.NO_USER) {
if (sharingRestrictedState == FeatureFlagState.FileSharingState.NoUser) {
R.string.file_sharing_restricted_description_no_users
} else {
R.string.file_sharing_restricted_description_by_team
Expand All @@ -301,7 +297,7 @@ fun FileSharingRestrictedContent(

Spacer(modifier = Modifier.height(dimensions().spacing16x))

if (sharingRestrictedState == FeatureFlagState.SharingRestrictedState.NO_USER) {
if (sharingRestrictedState == FeatureFlagState.FileSharingState.NoUser) {
WirePrimaryButton(
onClick = openWireAction,
text = stringResource(R.string.file_sharing_restricted_button_text_no_users),
Expand Down Expand Up @@ -459,14 +455,14 @@ private fun SnackBarMessage(
@Preview(showBackground = true)
@Composable
fun PreviewImportMediaScreenLoggedOut() {
ImportMediaLoggedOutContent(FeatureFlagState.SharingRestrictedState.NO_USER) {}
ImportMediaLoggedOutContent(FeatureFlagState.FileSharingState.NoUser) {}
}

@Preview(showBackground = true)
@Composable
fun PreviewImportMediaScreenRestricted() {
ImportMediaRestrictedContent(
FeatureFlagState.SharingRestrictedState.RESTRICTED_IN_TEAM,
FeatureFlagState.FileSharingState.DisabledByTeam,
ImportMediaAuthenticatedState()
) {}
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1396,4 +1396,6 @@
<string name="location_app_permission_dialog_body">Allow Wire to access your device location to send your location.</string>
<string name="location_loading_label">Please wait...</string>
<string name="location_could_not_be_shared">Location could not be shared</string>

<string name="restricted_asset_error_toast_message">Sending of files is forbidden due to company restrictions</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,21 @@ internal class MessageComposerViewModelArrangement {
} returns Unit
}

fun withSendAssetsResult(result: ScheduleNewAssetMessageResult) = apply {
coEvery {
sendAssetMessage(
any(),
any(),
any(),
any(),
any(),
any(),
any(),
any()
)
} returns result
}

fun withFailureOnDeletingMessages() = apply {
coEvery { deleteMessage(any(), any(), any()) } returns Either.Left(CoreFailure.Unknown(null))
return this
Expand Down
Loading
Loading